算法——贪心

博客围绕19个算法问题展开,如分发饼干、摆动序列等,详细阐述了贪心策略。贪心本质是选局部最优达全局最优,各问题通过不同局部最优策略求解,如大饼干喂大胃口小孩、删除单调坡度节点等,还给出部分问题的代码实现思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

「贪心的本质是选择每一阶段的局部最优,从而达到全局最优」

贪心无套路

1. 分发饼干

贪心策略:

(1)局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩

(2)局部最优就是小饼干喂给胃口小的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩

大饼干喂给胃口大的

// 冒泡排序
void sort(int *a, int n) {
    int flag = 1;
    for (int i = n - 1; flag && i > 0; i--) {
        flag = 0;
        for (int j = 0; j < i; j++) {
            if (a[j] > a[j + 1]) {
                flag = 1;
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }
}

int findContentChildren(int *g, int gSize, int *s, int sSize) {
    sort(g, gSize);
    sort(s, sSize);
    int j = gSize - 1;
    int sum = 0;
    // 大饼干喂给胃口大的
    for (int i = sSize - 1; i >= 0; i--) { // 饼干
        while (j >= 0 && s[i] < g[j]) {
            j--;
        }
        if (j >= 0) {
            sum++;
            j--;
        }
    }
    return sum;
}

小饼干喂给胃口小的

// 冒泡排序
void sort(int *a, int n) {
    int flag = 1;
    for (int i = n - 1; flag && i > 0; i--) {
        flag = 0;
        for (int j = 0; j < i; j++) {
            if (a[j] > a[j + 1]) {
                flag = 1;
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp; 
            }
        }
    }
}

int findContentChildren(int *g, int gSize, int *s, int sSize) {
    sort(g, gSize);
    sort(s, sSize);
    int j = 0;
    int sum = 0;
    // 小饼干喂给胃口小的
    for (int i = 0; i < gSize; i++) { // 胃口
        while (j < sSize && s[j] < g[i]) {
            j++;
        }
        if (j < sSize) {
            sum++;
            j++;
        }
    }
    return sum;
}

2. 摆动序列

暴力法:

int wiggleMaxLength(int *nums, int numsSize) {
    if (numsSize < 2) {
        return numsSize;
    }
    int sum = 1;
    for (int i = 1; i < numsSize; i++) { // 起始点
        int pre = nums[i] - nums[i - 1];
        if (pre != 0) {
            int len = 2;
            for (int j = i + 1; j < numsSize; j++) {
                int now = nums[j] - nums[j - 1];
                if ((pre < 0 && now > 0) || (pre > 0 && now < 0)) {
                    len++;
                    pre = now;
                }
            }
            if (len > sum) {
                sum = len;
            }
        }
    }
    return sum;
}

贪心策略:

「局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值」

「整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列」

「实际操作中,其实连删除的操作都不用做,因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量就可以了(相当于是删除单一坡度上的节点,然后统计长度)」

代码实现:

int wiggleMaxLength(int *nums, int numsSize) {
    if (numsSize <= 1) {
        return numsSize;
    }
    int now = 0; // 当前一对峰值
    int pre = 0; // 前一对峰值
    int result = 1;
    for (int i = 1; i < numsSize; i++) {
        now = nums[i] - nums[i - 1];
        // 出现峰值
        if ((now > 0 && pre <= 0) || (pre >= 0 && now < 0)) {
            result++;
            pre = now;
        }
    }
    return result;
}

3. 最大子数组和

暴力解法:超时

int maxSubArray(int *nums, int numsSize) {
    int result = INT32_MIN;
    int count = 0;
    for (int i = 0; i < numsSize; i++) { // 设置起始位置
        count = 0;
        for (int j = i; j < numsSize; j++) { // 每次从起始位置i开始遍历寻找最大值
            count += nums[j];
            result = count > result ? count : result;
        }
    }
    return result;
}

动规五步曲:

  • 确定dp数组(dp table)以及下标的含义

        dp[i]:以 i 结尾的最大连续子序列和为dp[i]

  • 确定递推公式(容斥原理)

dp[i]只有两个方向可以推出来:

  • dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和

  • nums[i],即:从头开始计算当前连续子序列和

一定是取最大的,所以dp[i] = max(dp[i - 1] + nums[i], nums[i]);

#define max(a, b) ((a) > (b) ? (a) : (b))

int maxSubArray(int *nums, int numsSize) {
    if (numsSize == 0) { // 特殊情况
        return 0;
    }
    int dp[numsSize];
    dp[0] = nums[0];
    int result = dp[0];
    for (int i = 1; i < numsSize; i++) {
        dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 状态转移方程
        result = max(result, dp[i]); // result 保存dp[i]的最大值
    }
    return result;
}

贪心策略:

局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小

全局最优:选取最大“连续和”

int maxSubArray(int *nums, int numsSize) {
    int result = INT32_MIN;
    int count = 0;
    for (int i = 0; i < numsSize; i++) {
        count += nums[i];
        if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
            result = count;
        }
        if (count <= 0) {
            count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
        }
    }
    return result;
}

4. 买卖股票的最佳时机 ||

贪心策略:

「局部最优:收集每天的正利润,全局最优:求得最大利润」

int maxProfit(int *prices, int pricesSize) {
    int minProfit = prices[0];
    int result = 0;
    for (int i = 1; i < pricesSize; i++) {
        if (prices[i] > minProfit) {
            result += prices[i] - minProfit;
            minProfit = prices[i];
        }
        if (prices[i] < minProfit) {
            minProfit = prices[i];
        }
    }
    return result;
}

5. 跳跃游戏

贪心策略:

「贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点」

bool canJump(int *nums, int numsSize) {
    if (numsSize == 1) {
        return true;
    }
    int cover = 0;
    for (int i = 0; i <= cover; i++) {
        cover = cover > i + nums[i] ? cover : i + nums[i];
        if (cover >= numsSize - 1) {
            return true;
        }
    }
    return false;
}

6. 跳跃游戏 ||

方法一:回溯 + 历史答案剪枝优化——超时

int *dis;

void dfs(int k, int startindex, int *nums, int numsSize) {
    if (dis[startindex] <= k) {
        return;
    }
    dis[startindex] = k;
    for (int i = 0; i <= nums[startindex]; i++) {
        if (startindex + i > numsSize -1) {
            continue;
        }
        dfs(k + 1, startindex + i, nums, numsSize);
    }
}


int jump(int *nums, int numsSize) {
    if (numsSize == 1) {
        return 0;
    }
    dis = malloc(sizeof(int) * numsSize);
    for (int i = 0; i < numsSize; i++) {
        dis[i] = numsSize + 1;
    }
    dfs(0, 0, nums, numsSize);
    return dis[numsSize - 1];
}

方法二:贪心

局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一

整体最优:一步尽可能多走,从而达到最小步数

  • 若当前覆盖最远距离下标不是终点,步数就加一,还需要继续走
  • 若当前覆盖最远距离下标就是终点,步数不用加一,结束循环返回结果

#define max(a, b) ((a) > (b) ? (a) : (b))
int jump(int *nums, int numsSize) {
    if (numsSize == 1) {
        return 0;
    }
    int cur = 0; // 当前覆盖最远距离下标
    int ans = 0; // 记录走的最大步数
    int next = 0; // 下一步覆盖最远距离下标
    for (int i = 0; i < numsSize; i++) {
        next = max(nums[i] + i, next); // 更新下一步覆盖最远距离下标
        if (i == cur) { // 遇到当前覆盖最远距离下标
            if (cur != numsSize - 1) { // 如果当前覆盖最远距离下标不是终点
                ans++; // 需要走下一步
                cur = next; // 更新当前覆盖最远距离下标(相当于加油了)
                if (next >= numsSize - 1) { // 下一步的覆盖范围已经可以达到终点,结束循环
                    break;
                }
            } else { // 当前覆盖最远距离下标是集合终点,不用做ans++操作了,直接结束
                break;
            }
        }
    }
    return ans;
}

7. K次取反后最大化的数组和

贪心策略:

        局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大

        如果将负数都转变为正数了,K依然大于0

        局部最优:只找数值最小的正整数进行反转,当前数值可以达到最大,全局最优:整个 数组和 达到最大

int find_min(int *nums, int numsSize) {
    int min = nums[0];
    int ind = 0;
    for (int i = 1; i < numsSize; i++) {
        if (nums[i] < min) {
            min = nums[i];
            ind = i;
        }
    }
    return ind;
}

int numsSum(int *nums, int numsSize) {
    int sum = 0;
    for (int i = 0; i < numsSize; i++) {
        sum += nums[i];
    }
    return sum;
}

int largestSumAfterKNegations(int *nums, int numsSize, int k) {
    while (k > 0) {
        int min_ind = find_min(nums, numsSize);
        if (nums[min_ind] >= 0) {
            break;
        }
        if (nums[min_ind] < 0) {
            nums[min_ind] = -nums[min_ind]; 
        }
        k--;
    }
    if (k != 0 && k % 2) {
        int min_ind = find_min(nums, numsSize);
        nums[min_ind] = -nums[min_ind];
    }
    int sum = numsSum(nums, numsSize);
    return sum;
}

8. 加油站

暴力解法:超时

int canCompleteCircuit(int *gas, int gasSize, int *cost, int costSize) {
    for (int i = 0; i < costSize; i++) {
        int rest = gas[i] - cost[i]; // 记录剩余油量
        int index = (i + 1) % costSize;
        while (rest > 0 && index != i) { // 模拟以i为起点行驶一圈
            rest += gas[index] - cost[index];
            index = (index + 1) % costSize;
        }
        // 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
        if (rest >= 0 && index == i) {            
            return i;
        }
    }
    return -1;
}

贪心策略:

每个加油站的剩余量rest[i]为gas[i] - cost[i]

i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,起始位置从i+1算起,再从0计算curSum

局部最优:当前累加rest[j]的和curSum一旦小于0,起始位置至少要是j+1,因为从j开始一定不行。全局最优:找到可以跑一圈的起始位置」

int canCompleteCircuit(int *gas, int gasSize, int *cost, int costSize) {
    int curSum = 0;
    int totalSum = 0;
    int start = 0;
    for (int i = 0; i < gasSize; i++) {
        curSum += gas[i] - cost[i];
        totalSum += gas[i] - cost[i];
        if (curSum < 0) {
            start = i + 1; // 起始位置更新为i+1
            curSum = 0; // curSum从0开始
        }
    }
    if (totalSum < 0) { // 说明怎么走都不可能跑一圈
        return -1;
    }
    return start;
}

9. 分发糖果

贪心策略:两边分别考虑,取最大

  • 从左到右遍历,只比较右边孩子评分比左边大的情况

  • 从右到左遍历,只比较左边孩子评分比右边大的情况

int candy(int *ratings, int ratingsSize) {
    int left[ratingsSize];
    for (int i = 0; i < ratingsSize; i++) {
        if (i > 0 && ratings[i] > ratings[i - 1]) {
            left[i] = left[i - 1] + 1;
        } else {
            left[i] = 1;
        }
    }
    int right[ratingsSize];
    for (int i = ratingsSize - 1; i >= 0; i--) {
        if (i < ratingsSize - 1 && ratings[i] > ratings[i + 1]) {
            right[i] = right[i + 1] + 1;
        } else {
            right[i] = 1;
        }
    }
    int num = 0;
    for (int i = 0; i < ratingsSize; i++) {
        num += left[i] > right[i] ? left[i] : right[i];
    }
    return num;
}

10. 柠檬水找零

bool lemonadeChange(int *bills, int billsSize) {
    int five = 0, ten = 0;
    for (int i = 0; i < billsSize; i++) {
        if (bills[i] == 5) {
            five++;
        } else if (bills[i] == 10) {
            ten++;
            five--;
        } else if (ten >= 1) {
            ten--;
            five--;
        } else {
            five -= 3;
        }
        if (five < 0) {
            return false;
        }
    }
    return true;
}

11. 根据身高重建队列

12. 用最少数量的箭引爆气球

贪心策略:

右边界排序之后,如果前一个边界的右边界 >= 后一个区间的左边界,则需要删除的重叠区间+1,否则,end更新为后一个区间的右边界

// 交换
void swap(int *m, int *n) {
    int temp = *m;
    *m = *n;
    *n = temp;
}

// 快排
void sort(int **nums, int l, int r) { // 左闭右开
    if (r - l <= 2) {
        if (r - l <= 1) { // 剩余个数:<= 1
            return;
        } 
        if (nums[r - 1][1] < nums[l][1]) { // 剩余个数:= 2 且后者小于前者 交换
            swap(&nums[r - 1][0], &nums[l][0]);
            swap(&nums[r - 1][1], &nums[l][1]);
        }
        return;
    }
    int x = nums[l][0];
    int y = nums[l][1];
    int i = l, j = r - 1;
    while (i < j) {
        while (i < j && nums[j][1] >= y) {
            j--;
        }
        if (i < j) {
            nums[i][0] = nums[j][0];
            nums[i][1] = nums[j][1];
            i++;
        }
        while (i < j && nums[i][1] <= y) {
            i++;
        }
        if (i < j) {
            nums[j][0] = nums[i][0];
            nums[j][1] = nums[i][1];
            j--;
        }
    }
    nums[i][0] = x, nums[i][1] = y;
    sort(nums, l, i);
    sort(nums, i + 1, r);
}


int findMinArrowShots(int **points, int pointsSize, int *pointsColSize){
    if (pointsSize <= 1) {
        return pointsSize;
    }
    // 按右区间快排
    sort(points, 0, pointsSize); // 左闭右开

    int end = points[0][1];
    int num = 1;
    for (int i = 1; i < pointsSize; i++) {
        if (points[i][0] > end) {
            num++;
            end = points[i][1];
        }
    }
    return num;
}

13. 无重叠区间

贪心策略:

右边界排序之后,如果前一个边界的右边界 > 后一个区间的左边界,则需要删除的重叠区间+1,否则,end更新为后一个区间的右边界

每次取重叠区间的时候,都是以右边界最小的来做分割点(这样留给下一个区间的空间就越大),所以第一条分割线就是区间1结束的位置

// 交换
void swap(int *m, int *n) {
    int temp = *m;
    *m = *n;
    *n = temp;
}

// 快排
void sort(int **nums, int l, int r) { // 左闭右开
    if (r - l <= 2) {
        if (r - l <= 1) { // 剩余个数:<= 1
            return;
        } 
        if (nums[r - 1][1] < nums[l][1]) { // 剩余个数:= 2 且后者小于前者 交换
            swap(&nums[r - 1][0], &nums[l][0]);
            swap(&nums[r - 1][1], &nums[l][1]);
        }
        return;
    }
    int x = nums[l][0];
    int y = nums[l][1];
    int i = l, j = r - 1;
    while (i < j) {
        while (i < j && nums[j][1] >= y) {
            j--;
        }
        if (i < j) {
            nums[i][0] = nums[j][0];
            nums[i][1] = nums[j][1];
            i++;
        }
        while (i < j && nums[i][1] <= y) {
            i++;
        }
        if (i < j) {
            nums[j][0] = nums[i][0];
            nums[j][1] = nums[i][1];
            j--;
        }
    }
    nums[i][0] = x, nums[i][1] = y;
    sort(nums, l, i);
    sort(nums, i + 1, r);
}

int eraseOverlapIntervals(int **intervals, int intervalsSize, int *intervalsColSize) {
    if (intervalsSize == 0) {
        return 0;
    }
    // 按右边界快排
    sort(intervals, 0, intervalsSize); // 左闭右开

    int count = 0; // 需要删除重叠区间
    int end = intervals[0][1]; // 记录区间分割点
    for (int i = 1; i < intervalsSize; i++) {
        if (end <= intervals[i][0]) {
            end = intervals[i][1];
        } else {
            count++;
        }
    }
    return count;
}

14. 划分字母区间

贪心策略:

找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了」

  • 统计每一个字符最后出现的位置

  • 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点

代码实现:

(1)递归

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
void dfs(char *s, int start, int *res, int *returnSize, int *hash) {
    int l = start;
    int r = hash[s[start] - 'a'];
    // 找到当前字母区间的结束位置
    for (int i = l; i < strlen(s); i++) {
        if (hash[s[i] - 'a'] > r) {
            r = hash[s[i] - 'a'];
        }
        if (i == r) {
            // 输出当前字母区间
            res[(*returnSize)++] = r - l + 1;
            // 递归处理下一个字母区间
            dfs(s, r + 1, res, returnSize, hash);
            return ;
        }
    }
}

int* partitionLabels(char *s, int *returnSize) {
    if (s == NULL || strlen(s) == 0) {
        return NULL;
    }
    int *res = malloc(sizeof(int) * strlen(s));
    *returnSize = 0;
    int hash[26] = {0}; // 用于存储每个字母最后出现的位置
    for (int i = 0; i < strlen(s) ; i++) {
        hash[s[i] - 'a'] = i;
    }
    dfs(s, 0, res, returnSize, hash);
    return res;
}

(2)循环

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* partitionLabels(char *s, int *returnSize) {
    int hash[27] = {0}; // i为字符,hash[i]为字符出现的最后位置
    for (int i = 0; i < strlen(s); i++) { // 统计每一个字符最后出现的位置
        hash[s[i] - 'a'] = i;
    }
    *returnSize = 0;
    int *result = malloc(sizeof(int) * strlen(s));
    int left = 0;
    int right = 0;
    for (int i = 0; i < strlen(s); i++) {
        // 找到字符出现的最远边界
        right = right > hash[s[i] - 'a'] ? right : hash[s[i] - 'a'];
        if (i == right) {
            result[(*returnSize)++] = right - left + 1;
            left = i + 1;
        }
    }
    return result;
}

15. 合并区间

贪心策略:

按左边界从小到大排序,如果前一个区间的右边界大于等于后一个区间的左边界,就合并,合并之后的区间左边界为前一个区间的左边界,右边界为后一个区间的右边界

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */

// 交换
void swap(int *m, int *n) {
    int temp = *m;
    *m = *n;
    *n = temp;
}

// 快排
void sort(int **nums, int l, int r) { // 左闭右开
    if (r - l <= 2) {
        if (r - l <= 1) { // 剩余个数:<= 1
            return;
        } 
        if (nums[r - 1][0] < nums[l][0]) { // 剩余个数:= 2 且后者小于前者 交换
            swap(&nums[r - 1][0], &nums[l][0]);
            swap(&nums[r - 1][1], &nums[l][1]);
        }
        return;
    }
    int x = nums[l][0];
    int y = nums[l][1];
    int i = l, j = r - 1;
    while (i < j) {
        while (i < j && nums[j][0] >= x) {
            j--;
        }
        if (i < j) {
            nums[i][0] = nums[j][0];
            nums[i][1] = nums[j][1];
            i++;
        }
        while (i < j && nums[i][0] <= x) {
            i++;
        }
        if (i < j) {
            nums[j][0] = nums[i][0];
            nums[j][1] = nums[i][1];
            j--;
        }
    }
    nums[i][0] = x, nums[i][1] = y;
    sort(nums, l, i);
    sort(nums, i + 1, r);
}

int** merge(int **intervals, int intervalsSize, int *intervalsColSize, int *returnSize, int **returnColumnSizes){
    if (intervalsSize == 0) {
        return NULL;
    }
    *returnSize = 0;
    // 按左边界(从小到大)快排
    sort(intervals, 0, intervalsSize); // 左闭右开

    *returnColumnSizes = malloc(sizeof(int) * intervalsSize);
    int **res = malloc(sizeof(int*) * intervalsSize); // 记录结果
    int left = intervals[0][0];
    int right = intervals[0][1];
    for (int i = 1; i < intervalsSize; i++) {
        if (right >= intervals[i][0]) {
            right = right > intervals[i][1] ? right : intervals[i][1];
        } else {
            (*returnColumnSizes)[(*returnSize)] = 2;
            res[(*returnSize)] = malloc(sizeof(int) * 2);
            res[(*returnSize)][0] = left;
            res[(*returnSize)][1] = right;
            (*returnSize)++;
            left = intervals[i][0];
            right = intervals[i][1];
        }
    }
    // 记录合并的最后一个区间
    (*returnColumnSizes)[(*returnSize)] = 2;
    res[(*returnSize)] = malloc(sizeof(int) * 2);
    res[(*returnSize)][0] = left;
    res[(*returnSize)][1] = right;
    (*returnSize)++;

    return res;
}

16. 单调递增的数字

方法一:暴力解法——超时

bool checkN(int num) {
    int max = 10;
    while (num) {
        int t = num % 10;
        if (max >= t) {
            max = t;
        } else {
            return false;
        }
        num /= 10;
    }
    return true;
}

int monotoneIncreasingDigits(int n) {
    for (int i = n; i > 0; i--) {
        if (checkN(i)) {
            return i;
        }
    }
    return 0;
}

方法二:贪心策略

既然要尽可能的大,那么这个数从高位开始要尽可能地保持不变。找到从高到低第一个满足 str[i] > str[i+1] 的位置,然后把 str[i] − 1,再把后面的位置都变成 9即可

int monotoneIncreasingDigits(int n){
    char ans[11] = {0};
    sprintf(ans, "%d", n); // 转换n为字符串
    int len = strlen(ans);
    bool flag = false;
    for (int i = 0; i < len; i++) { // 遍历字符串寻找改变位置
        if (flag) { // 已经找到改变位置了,直接赋9
            ans[i] = '9';
            continue;
        }
        if (i != len - 1 && ans[i] > ans[i + 1]) { // 寻找第一个改变位
            while (i >= 1 && ans[i] == ans[i - 1]) {
                i--; // 排出相等情况
            }
            ans[i]--;
            flag = true;
        }
    }
    sscanf(ans, "%d", &n);
    return n;
}

17. 买卖股票的最佳时机含手续费

贪心策略:最低值买,最高值(如果算上手续费还盈利)就卖

  • 买入日期:遇到更低点就记录一下

  • 卖出日期:没有必要算出准确的卖出日期,只要当前价格大于(最低价格+手续费),就可以收获利润,至于准确的卖出日期,就是连续收获利润区间里的最后一天(并不需要计算是具体哪一天)

收获利润操作有三种情况:

  • 情况一:收获利润的这一天并不是收获利润区间里的最后一天(不是真正的卖出,相当于持有股票),所以后面要继续收获利润

  • 情况二:前一天是收获利润区间里的最后一天(相当于真正的卖出了),今天要重新记录最小价格了

  • 情况三:不作操作,保持原有状态(买入,卖出,不买不卖)

代码实现:

int maxProfit(int *prices, int pricesSize, int fee) {
    int result = 0;
    int minPrice = prices[0]; // 记录最低价格
    for (int i = 1; i < pricesSize; i++) {
        // 情况二:相当于买入
        if (prices[i] < minPrice) {
            minPrice = prices[i];
        }
        // 情况三:保持原有状态(因为买则不便宜,卖则亏本)
        if (prices[i] >= minPrice && prices[i] <= minPrice + fee) {
            continue;
        }
        // 计算利润,可能有多次计算利润,最后一次计算利润才是真正意义的卖出
        if (prices[i] > minPrice + fee) {
            result += prices[i] - minPrice - fee; 
            minPrice = prices[i] - fee; // 情况一,这一步很关键
        }
    }
    return result;
}

18. 监控二叉树

贪心策略:
从下往上看

局部最优:让叶子节点的父节点安摄像头,所用摄像头最少

整体最优:全部摄像头数量所用最少!

1) 二叉树的遍历

        后序遍历

2) 如何隔两个节点放一个摄像头

每个节点都可能有以下三种状态:

  • 该节点无覆盖

  • 本节点有摄像头

  • 本节点有覆盖

分别用三个数字来表示:

  • 0:该节点无覆盖

  • 1:本节点有摄像头

  • 2:本节点有覆盖

3) 单层逻辑处理

  • 情况1:左右节点都有覆盖

左孩子有覆盖,右孩子有覆盖,那么此时中间节点应该就是无覆盖的状态了

  • 情况2:左右节点至少有一个无覆盖的情况

left == 0 && right == 0 左右节点无覆盖
left == 1 && right == 0 左节点有摄像头,右节点无覆盖
left == 0 && right == 1 左节点无覆盖,右节点摄像头
left == 0 && right == 2 左节点无覆盖,右节点覆盖
left == 2 && right == 0 左节点覆盖,右节点无覆盖

如果是以上情况,则中间节点(父节点)应该放摄像头

  • 情况3:左右节点至少有一个有摄像头

left == 1 && right == 2 左节点有摄像头,右节点有覆盖
left == 2 && right == 1 左节点有覆盖,右节点有摄像头
left == 1 && right == 1 左右节点都有摄像头

以上三种情况,其实就是 左右孩子节点至少有一个有摄像头了且另一个节点被覆盖,那么其父节点就应该是2(覆盖的状态)

代码实现:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
/*
    0:该节点无覆盖
    1:本节点有摄像头
    2:本节点有覆盖
*/
int result;
int postorder(struct TreeNode *root) {
    // 空结点,该节点有覆盖
    if (root == NULL) {
        return 2;
    }
    int l = postorder(root->left); // 左
    int r = postorder(root->right); // 右
    
    // 情况1
    // 左右节点都有覆盖
    if (l == 2 && r == 2) {
        return 0;
    }

    // 情况2
    // l == 0 && r == 0 左右节点无覆盖
    // l == 1 && r == 0 左节点有摄像头,右节点无覆盖
    // l == 0 && r == 1 左节点无覆盖,右节点摄像头
    // l == 0 && r == 2 左节点无覆盖,右节点覆盖
    // l == 2 && r == 0 左节点覆盖,右节点无覆盖
    if (l == 0 || r == 0) {
        result++;
        return 1;
    }

    // 情况3
    // l == 1 && r == 2 左节点有摄像头,右节点有覆盖
    // l == 2 && r == 1 左节点有覆盖,右节点有摄像头
    // l == 1 && r == 1 左右节点都有摄像头
    // 其他情况前段代码均已覆盖
    if (l == 1 || r == 1) {
        return 2;
    }

    // 这个 return -1 逻辑不会走到这里
    return -1;
}
int minCameraCover(struct TreeNode *root) {
    result = 0;
    if (postorder(root) == 0) { // root 无覆盖
        result++;
    }
    return result;
}

精简代码:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
/*
    0:该节点无覆盖
    1:本节点有摄像头
    2:本节点有覆盖
*/
int result;
int postorder(struct TreeNode *root) {
    // 空结点,该节点有覆盖
    if (root == NULL) {
        return 2;
    }
    int l = postorder(root->left); // 左
    int r = postorder(root->right); // 右

    if (l == 2 && r == 2) {
        return 0;
    } else if (l == 0 || r == 0) {
        result++;
        return 1;
    } else {
        return 2;
    }
}
int minCameraCover(struct TreeNode *root) {
    result = 0;
    if (postorder(root) == 0) { // root 无覆盖
        result++;
    }
    return result;
}

19. 修改后的最大二进制字符串


代码实现:

思路:从前往后遍历,如果当前字符是0,当前字符的下一个字符也是0,则把当前字符变为1;如果当前字符的下一个字符不是0,从当前字符往后找,找到第一个字符0的位置,将当前字符0换成1,当前字符的下一个位置字符1换成0,找到的第一个字符0换成1,将如果没找到,则已经得到了修改后的最大二进制字符串,结束程序

方法一:j 每次从 i + 1位置开始——超时

char* maximumBinaryString(char *binary) {
    int n = strlen(binary);
    for (int i = 0; i < n; i++) {
        if (binary[i] == '0') {
            int j = i + 1;
            while (j < n && binary[j] == '1') {
                j++;
            }
            if (j < n) {
                binary[j] = '1';
                binary[i] = '1';
                binary[i + 1] = '0';
            }
        }
    }
    return binary;
}

方法二:

char* maximumBinaryString(char *binary) {
    int n = strlen(binary);
    char *s = malloc(n + 1);
    strcpy(s, binary);
    int j = 0;
    for (int i = 0; i < n; i++) {
        if (s[i] == '0') {
            while (j <= i || (j < n && s[j] == '1')) {
                j++;
            }
            if (j < n) {
                s[j] = '1';
                s[i] = '1';
                s[i + 1] = '0';
            }
        }
    }
    return s;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值