9、贪心算法相关

上一节:8、回溯算法相关

下一节:10、动态规划相关

贪心算法

1、概念

什么是贪心

贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
这么说有点抽象,来举一个例子:
例如,有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?
指定每次拿最大的,最终结果就是拿走最大数额的钱。
每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。

贪心的套路(什么时候用贪心)

说实话贪心算法并没有固定的套路。
所以唯一的难点就是如何通过局部最优,推出整体最优。
那么如何能看出局部最优是否能推出整体最优呢?有没有什么固定策略或者套路呢?
不好意思,也没有! 靠自己手动模拟,如果模拟可行,就可以试一试贪心策略,如果不可行,可能需要动态规划。
有同学问了如何验证可不可以用贪心算法呢?
最好用的策略就是举反例,如果想不到反例,那么就试一试贪心吧。

贪心一般解题步骤

贪心算法一般分为如下四步:

  • 将问题分解为若干个子问题
  • 找出适合的贪心策略
  • 求解每一个子问题的最优解
  • 将局部最优解堆叠成全局最优解

其实这个分的有点细了,真正做题的时候很难分出这么详细的解题步骤,可能就是因为贪心的题目往往还和其他方面的知识混在一起。

2、题目

leetcode 455. 分发饼干

leetcode 455. 分发饼干

在这里插入图片描述

  • 局部最优就是:大饼干优先给大孩子
  • 全局最优就是:喂饱尽可能多的小孩

代码如下:(先把大饼干喂给大孩子)

int cmp(const void* a, const void* b) {
    return *(int*)a - *(int*)b;
}
int findContentChildren(int* g, int gSize, int* s, int sSize) {
    /*特判:没有小孩、或没有饼干,返回0 */
    if (gSize == 0 || sSize == 0) {
        return 0;
    }
    /* 小孩胃口升序排序 */
    qsort(g, gSize, sizeof(int), cmp);
    /* 饼干尺寸升序排序 */
    qsort(s, sSize, sizeof(int), cmp);
    /* 返回值 */
    int res = 0;
    /* 饼干数组下标索引 */
    int index = sSize - 1;
    /* 从大到小遍历小孩的胃口 */
    for (int i = gSize - 1; i >= 0; i--) {
        /* 若当前饼干没有分配完、或当前饼干尺寸大于等于当前小孩的胃口 */
        if (index >= 0 && s[index] >= g[i]) {
            /* 返回值加一 */
            res++;
            /* 饼干索引减一 */
            index--;
        }
    }
    return res;
}

leetcode 376. 摆动序列

leetcode 376. 摆动序列
在这里插入图片描述
具体看题解:

int wiggleMaxLength(int* nums, int numsSize) {
    /* 特判 */
    if (numsSize < 2) {
        return numsSize; 
    }
    /* 返回值初值为1 */
    int res = 1;
    /* preDiff和curDiff初值为0 */
    int preDiff = 0;
    int curDiff = 0;
    /* 从0到numsSize - 2 */
    for (int i = 0; i < numsSize - 1; i++) {
        /* 计算的当前两元素差值 */
        curDiff = nums[i + 1] - nums[i];
        /* 这里只考虑curDiff为正、preDiff为非正;
         * curDiff为负、preDiff为非负两种情况;
         * 不考虑curDiff为0的情况,curDiff为0时跳过比较,因为坡度为0
         */
        if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {
            res++;
            preDiff = curDiff;
        }
    }
    return res;
}

leetcode 53. 最大子序和

leetcode 53. 最大子序和
在这里插入图片描述

  • 局部最优就是当前元素之和大于0,每次都比较当前和count 和res的值,遇到count > res,就更新res为count,遇到count <= 0时,count从0开始重新计算
  • 全局最优就是遍历完整个数组找到最大的res

代码如下:

int maxSubArray(int* nums, int numsSize) {
    /* 返回值为INT类型最小值 */
    int res = INT_MIN;
    /* 计数count初值为0 */
    int count = 0;
    /* 从头到尾遍历数组 */
    for (int i = 0; i < numsSize; i++) {
        /* count先加 */
        count += nums[i];
        /* 再比较返回值 */
        if (count > res) {
            res = count;
        }
        /* 再更新count,出现非正既可清零 */
        if (count <= 0) {
            count = 0;
        }
    }
    return res;
}

leetcode 122. 买卖股票的最佳时机 II

leetcode 122. 买卖股票的最佳时机 II
在这里插入图片描述
在这里插入图片描述

  • 局部最优就是收集每天的正利润
  • 全局最优就是将所有正利润相加

代码如下:

int maxProfit(int* prices, int pricesSize) {
    int res = 0;
    for (int i = 1; i < pricesSize; i++) {
        res += (int)fmax(prices[i] - prices[i - 1], 0);
    }
    return res;
}

leetcode 55. 跳跃游戏

leetcode 55. 跳跃游戏
在这里插入图片描述

  • 局部最优就是每次记录count为能跳跃的最大值
  • 全局最优就是看count能否达到numsSize - 1

代码如下:

bool canJump(int* nums, int numsSize) {
    /* 特判,输入边界为 1 <= nums.length <= 3 * 104 
     * 那么当输入一个元素时可以达到最后一个下标
     */
    if (numsSize < 2) {
        return true;
    }
    /* 计数count初值为0 */
    int count = 0;
    /* 这里i范围是 i <= count 这样保证i在当前可达的最大范围之内前进 */
    for (int i = 0; i <= count; i++) {
        /* 更新count */
        count = (int)fmax(count, i + nums[i]);
        /* 找到一个能够达到最后的值即可返回 */
        if (count >= numsSize - 1) {
            return true;
        }
    }
    return false;
}

leetcode 45. 跳跃游戏 II

leetcode 45. 跳跃游戏 II
在这里插入图片描述

  • 局部最优,每次都要走到当前距离的最后
  • 当前距离走满之后,跳跃步数 res + 1;
  • 全局最优,每次走满当前距离需要跳跃的步数就是最少的

代码如下:

int jump(int* nums, int numsSize) {
    /* 输入元素只有一个,返回0表示不需要跳即可到达 */
    if (numsSize == 1) {
        return 0;
    }
    /* 返回值,跳跃步数 */
    int res = 0;
    /* 当前可达到最远距离 */
    int curDistance = 0;
    /* 下一次可达到最远距离 */
    int nextDistance = 0;
    for (int i = 0; i < numsSize; i++) {
        /* 更新下一次可达到最远距离 */
        nextDistance = (int)fmax(nextDistance, i + nums[i]); 
        /* 若达到当前元素可达到最远距离 */   
        if (i == curDistance) {
            /* 若当前位置已经是最大下标,返回即可 */
            if (curDistance == numsSize - 1) {
                break;
            } else {    /* 若当前位置不是最大下标 */
                /* 返回值加一 */
                res++;
                /* 更新当前可达到最远距离 */
                curDistance = nextDistance;
                /* 若此时下一次可达到的最远距离超过最大下标,返回即可 */
                if (nextDistance >= numsSize - 1) {
                    break;
                }
            }
        }
    }
    return res;
}

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

leetcode 1005. K 次取反后最大化的数组和
在这里插入图片描述

  • 局部最优,将绝对值大的负数取反,剩余k的次数全部作用在绝对值最小的数上面
  • 全局最优,就是遍历之后将所有数相加

代码如下:

int cmp(const void* a, const void* b) {
    const int* aa = a;
    const int* bb = b;
    if (abs(*bb) < abs(*aa)) {
        return -1;
    } else if (abs(*bb) == abs(*aa)) {
        return 0;
    } else {
        return 1;
    }
    //这种直接对数据进行加减的操作可能造成数据溢出
    //return abs(*(int*)b) - abs(*(int*)a);
}
int largestSumAfterKNegations(int* nums, int numsSize, int k){
    int res = 0;
    /* 按照元素绝对值降序排序 */
    qsort(nums, numsSize, sizeof(int), cmp);
    /* 先将绝对值大的负数反转 */
    for (int i = 0; i < numsSize; i++) {
        if (nums[i] < 0 && k > 0) {
            nums[i] *= -1;
            k--;
        }
    }
    /* 若k还有剩余,直接反转最有一个元素 */
    if (k % 2 == 1) {
        nums[numsSize - 1] *= -1;
    }
    /* 求和 */
    for (int i = 0; i < numsSize; i++) {
        res += nums[i];
    }
    return res;
}

leetcode 134. 加油站

leetcode 134. 加油站
在这里插入图片描述
在这里插入图片描述
如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。

每个加油站的剩余量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) {
            curSum = 0;
            start = i + 1;
        }
    }
    if (totalSum >= 0) {
        return start;
    }
    return -1;
}

leetcode 135. 分发糖果

leetcode 135. 分发糖果
在这里插入图片描述

  • 先考虑右边孩子 大于 左边孩子 (从前向后遍历)
  • 再考虑左边孩子 大于 右边孩子 (从后向前遍历,这样才能使用前一次遍历的结果)
  • 每一次遍历的局部最优都是确定一个方向上的条件满足
  • 全局最优就是两个方向都满足条件时,要发的糖果数量

代码如下:

int candy(int* ratings, int ratingsSize) {
    /* 所有孩子分配一个糖果 */
    int* res = (int*)calloc(ratingsSize, sizeof(int));
    for (int i = 0; i < ratingsSize; i++) {
        res[i] = 1;
    }
    /* 先考虑右边孩子大与左边孩子的情况
     * 右边 大于 左边,右边就是左边加一 
     * 局部最优:右边都保持比左边大
     */
    for (int i = 1; i < ratingsSize; i++) {
        if (ratings[i] > ratings[i - 1]) {
            res[i] = res[i - 1] + 1;
        }
    }
    /* 再考虑左边孩子大与右边孩子的情况
     * 左边 大于 右边有两种选择:
     * 左边 = max(右边加一, 左边加一)
     * 局部最优:左边都保持比右边大
     */
    for (int i = ratingsSize - 2; i >= 0; i--) {
        if (ratings[i] > ratings[i + 1]) {
            res[i] = (int)fmax(res[i], res[i + 1] + 1);
        }
    }
    /* 全局最优:每一个比相邻两孩子大的孩子获得更多的糖果 */
    /* 统计结果 */
    int result = 0;
    for (int i = 0; i < ratingsSize; i++) {
        result += res[i];
    }
    return result;
}

leetcode 860. 柠檬水找零

leetcode 860. 柠檬水找零
在这里插入图片描述
在这里插入图片描述
入帐分为三种情况:

  • 情况一:账单是5,直接收下。
  • 情况二:账单是10,消耗一个5,增加一个10
  • 情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5

此时大家就发现 情况一,情况二,都是固定策略,都不用我们来做分析了,而唯一不确定的其实在情况三。

而情况三逻辑也不复杂甚至感觉纯模拟就可以了,其实情况三这里是有贪心的。

账单是20的情况,为什么要优先消耗一个10和一个5呢?

因为美元10只能给账单20找零,而美元5可以给账单10和账单20找零,美元5更万能!

所以局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。

代码如下:

bool lemonadeChange(int* bills, int billsSize) {
    int five = 0, ten = 0, twenty = 0;
    for (int i = 0; i < billsSize; i++) {
        if (bills[i] == 5) {
            five++;
        } else if (bills[i] == 10) {
            if (five <= 0) {
                return false;
            }
            five--;
            ten++;
        } else {
            if (five > 0 && ten > 0) {
                /* 优先处理5元和10元 */
                five--;
                ten--;
                twenty++;
            } else if (five >= 3) {
                /* 10元不够再处理5元 */
                five -= 3;
                twenty++;
            } else {
                return false;
            }
        }
    }
    return true;
}

leetcode 406. 根据身高重建队列

leetcode 406. 根据身高重建队列
在这里插入图片描述

  • 局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性
  • 全局最优:最后都做完插入操作,整个队列满足题目队列属性

代码如下: queue也可以用list, 具体参考代码随想录 的讲解

class Solution {
public:
    static bool cmp(const vector<int> a, const vector<int> b) {
        if (a[0] == b[0]) {
            return a[1] < b[1];
        }
        return a[0] > b[0];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(), people.end(), cmp);
        vector<vector<int>> queue;
        for (int i = 0; i < people.size(); i++) {
            int pos = people[i][1];
            queue.insert(queue.begin() + pos, people[i]);
        }
        return queue;
    }
};

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

leetcode 452. 用最少数量的箭引爆气球
在这里插入图片描述
在这里插入图片描述

1、把数组按照气球的起点进行升序排列
    按照气球起始位置、终止位置排序都可以!只不过对应的遍历顺序不同,
    按照起始位置排序,那么就从前向后遍历气球数组,靠左尽可能让气球重复。
2、统计气球是否相邻,是则引爆气球
3、直觉上来看,貌似只射重叠最多的气球,用的弓箭一定最少,那么有没有当前重叠了三个气球,
    我射两个,留下一个和后面的一起射这样弓箭用的更少的情况呢?尝试一下举反例,发现没有这种情况。
    若气球[1、2][2、3][3、4][4、5],引爆2、4两点为两次
    引爆2、3、4为3次
4、程序可以从1向后遍历,若points[i][0] > points[i - 1][1],那么res++;res初始值为1;
    若points[i][0] <= points[i - 1][1],那么points[i][1] = (int)fmin(points[i][1], points[i - 1][1]),
    这样更新重叠气球最小右边界,就是将重叠气球看成一个整体

代码如下:

int cmp(const void* a, const void* b) {
    int* A = *(int**)a;
    int* B = *(int**)b;
    return *A > *B;
}

int findMinArrowShots(int** points, int pointsSize, int* pointsColSize) {
    if (pointsSize == 0) {
        return 0;
    }
    qsort(points, pointsSize, sizeof(points[0]), cmp);
    int res = 1;
    for (int i = 1; i < pointsSize; i++) {
        if (points[i][0] > points[i - 1][1]) {
            res++;
        } else {
            points[i][1] = (int)fmin(points[i][1], points[i - 1][1]);
        }
    }
    return res;
}

leetcode 435. 无重叠区间

leetcode 435. 无重叠区间
此题也参考leetcode 452. 用最少数量的箭引爆气球
在这里插入图片描述

1、此题是删除区间使得剩下的区间不重叠,452题是求重叠的区间
2、用区间个数减去重叠区间的个数就是不重叠区间的个数了
3、将452题改一下即可,在for循环中 
    if (intervals[i][0] >= intervals[i - 1][1])>= 不是 > 因为此题的接触不是重合

代码如下:

int cmp(const void* a, const void* b) {
    int* A = *(int**)a;
    int* B = *(int**)b;
    return A[1] > B[1];
}

int eraseOverlapIntervals(int** intervals, int intervalsSize, int* intervalsColSize) {
    if (intervalsSize == 0) {
        return 0;
    }
    /* 按照区间右端点升序排序 */
    qsort(intervals, intervalsSize, sizeof(intervals[0]), cmp);
    int res = 1;
    for (int i = 1; i < intervalsSize; i++) {
        if (intervals[i][0] >= intervals[i - 1][1]) {
            res++;
        } else {
            intervals[i][1] = (int)fmin(intervals[i][1], intervals[i - 1][1]);
        }
    }
    return intervalsSize - res;
}

leetcode 763. 划分字母区间

leetcode 763. 划分字母区间
在这里插入图片描述

此题将分割字符串转化为查找元素最远下标;

  • 1、将数组中元素建立哈希表,记录出现的最远下标
  • 2、从头到尾遍历数组、每次更新当前最远下标right的值,直到出现s[i] == right即找到分割点

代码如下:

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* partitionLabels(char * s, int* returnSize) {
    /* 建立元素最远出现下标的hash表 */
    int sLen = strlen(s);
    int* hash = (int*)malloc(sizeof(int) * 27);
    for (int i = 0; i < sLen; i++) {
        hash[s[i] - 'a'] = i;
    }
    /* 返回数组对多为sLen长度 */
    int* res = (int*)calloc(sLen, sizeof(int));
    int left = 0, right = 0;
    *returnSize = 0;
    /* 从头到尾遍历计算 */
    for (int i = 0; i < sLen; i++) {
        /* 更新当前最远的下标 */
        right = (int)fmax(right, hash[s[i] - 'a']);
        /* 若当前元素即为最远下标,则找到一个分割点 */
        if (i == right) {
            res[(*returnSize)++] = right - left + 1;
            left = i + 1;
        }
    }
    return res;
}

leetcode 56. 合并区间

leetcode 56. 合并区间
在这里插入图片描述

1、按照intervals[0]进行升序排序
2、依次遍历数组
if (intervals[i][0] > intervals[i - 1][1]) {
    /* 当前元素不在上一个区间,更新区间列表 */
    res[*returnSize] = (int*)malloc(sizeof(int) * (2));
    res[*returnSize][0] = intervals[i - 1][0];
    res[*returnSize][1] = intervals[i - 1][1];
    (*returnColumnSizes)[(*returnSize)++] = 2;
    left = i;
} else {
    /* 当前元素还在上一个区间,更新此区间的左右边界 */
    intervals[i][0] = (int)fmin(intervals[i - 1][0], intervals[i][0]);
    intervals[i][1] = (int)fmax(intervals[i - 1][1], intervals[i][1]);
}
3、更新最后的一个区间

代码如下:

/**
 * 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().
 */
int cmp(const void* a, const void* b) {
    int* A = *(int**)a;
    int* B = *(int**)b;
    return A[0] > B[0];
}
int** merge(int** intervals, int intervalsSize, int* intervalsColSize, int* returnSize, int** returnColumnSizes) {
    /* 返回数组 */
    int** res = (int**)malloc(sizeof(int*) * intervalsSize);
    *returnColumnSizes = (int*)malloc(sizeof(int) * intervalsSize);
    *returnSize = 0;
    /* left用来标记每个合并后的区间起始点 */
    int left = 0;
    /* 按照数组intervals[0]进行升序排序 */
    qsort(intervals, intervalsSize, sizeof(intervals[0]), cmp);
    for (int i = 1; i < intervalsSize; i++) {
        if (intervals[i][0] > intervals[i - 1][1]) {
            /* 当前元素不在上一个区间,更新区间列表 */
            res[*returnSize] = (int*)malloc(sizeof(int) * (2));
            res[*returnSize][0] = intervals[i - 1][0];
            res[*returnSize][1] = intervals[i - 1][1];
            (*returnColumnSizes)[(*returnSize)++] = 2;
            left = i;
        } else {
            /* 当前元素还在上一个区间,更新此区间的左右边界 */
            intervals[i][0] = (int)fmin(intervals[i - 1][0], intervals[i][0]);
            intervals[i][1] = (int)fmax(intervals[i - 1][1], intervals[i][1]);
        }
    }
    /* 更新最后的一个区间 */
    res[*returnSize] = (int*)malloc(sizeof(int) * (2));
    res[*returnSize][0] = intervals[left][0];
    res[*returnSize][1] = intervals[intervalsSize - 1][1];
    (*returnColumnSizes)[(*returnSize)++] = 2;
    return res;
}

leetcode 738. 单调递增的数字

leetcode 738. 单调递增的数字
在这里插入图片描述
参考 代码随想录 的讲解
题目要求小于等于N的最大单调递增的整数,那么拿一个两位的数字来举例。
例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]–,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数。
这一点如果想清楚了,这道题就好办了。

局部最优 遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]–,然后strNum[i]给为9,可以保证这两位变成最大单调递增整数。
全局最优 得到小于等于N的最大单调递增的整数。

但这里局部最优推出全局最优,还需要其他条件,即遍历顺序,和标记从哪一位开始统一改成9。
此时是从前向后遍历还是从后向前遍历呢?
从前向后遍历的话,遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]减一,但此时如果strNum[i - 1]减一了,可能又小于strNum[i - 2]。
这么说有点抽象,举个例子,数字:332,从前向后遍历的话,那么就把变成了329,此时2又小于了第一位的3了,真正的结果应该是299。

所以从前后向遍历会改变已经遍历过的结果!
那么从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299
确定了遍历顺序之后,那么此时局部最优就可以推出全局,找不出反例,试试贪心。

代码如下:

class Solution {
public:
    int monotoneIncreasingDigits(int n) {
        /* 将整数转为字符串 */
        string strNum = to_string(n);
        /* flag用来标记最后一个数字转换为位置 */
        int flag = strNum.size();
        for (int i = strNum.size() - 1; i > 0; i--) {
            /* 前一位置 大于 当前位置时,前一位置减一*/
            if (strNum[i - 1] > strNum[i]) {
                strNum[i - 1]--;
                flag = i;
            }
        }
        /* 从前向后将当前位置替换为9 */
        for (int i = flag; i < strNum.size(); i++) {
            strNum[i] = '9';
        }
        /* 转为整数 */
        return stoi(strNum);
    }
};

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

leetcode 714. 买卖股票的最佳时机含手续费
在这里插入图片描述

如果使用贪心策略,就是最低值买,最高值(如果算上手续费还盈利)就卖。
此时无非就是要找到两个点,买入日期,和卖出日期。

买入日期:其实很好想,遇到更低点就记录一下。
卖出日期:这个就不好算了,但也没有必要算出准确的卖出日期,只要当前价格大于(最低价格+手续费),
就可以收获利润,至于准确的卖出日期,就是连续收获利润区间里的最后一天(并不需要计算是具体哪一天)。
所以我们在做收获利润操作的时候其实有三种情况:

情况一:收获利润的这一天并不是收获利润区间里的最后一天(不是真正的卖出,相当于持有股票),所以后面要继续收获利润。
情况二:前一天是收获利润区间里的最后一天(相当于真正的卖出了),今天要重新记录最小价格了。
情况三:不作操作,保持原有状态(买入,卖出,不买不卖)

代码如下:

int maxProfit(int* prices, int pricesSize, int fee) {
    int res = 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) {
            res += prices[i] - minPrice - fee;
            /* 情况一,这一步很关键
             * 如果是第一次卖出的话,这里的res就是正确的
             * 如果遇到更高的卖出价格,需要重新卖出,那么minprice需要减去fee,
             * 这样在后面再卖出的话就是prices[i] - prices[上一次卖出的价格] + fee - fee
             * 这样就相当于 第一次卖出时减掉手续费,后面卖出时不减,这样就只减一次手续费
             */
            minPrice = prices[i] - fee; 
        }
    }
    return res;
}

leetcode 968. 监控二叉树

leetcode 968. 监控二叉树
在这里插入图片描述
在这里插入图片描述
具体参考 代码随想录 的讲解
代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
int res;

int traversal(struct TreeNode* cur) {
    // 空节点,该节点有覆盖
    if (!cur) {
        return 2;
    }
    /* 递归左右子树 */
    int left = traversal(cur->left);
    int right = traversal(cur->right);
    // 情况1
    // 左右节点都有覆盖
    if (left == 2 && right == 2) return 0;
    // 情况2
    // left == 0 && right == 0 左右节点无覆盖
    // left == 1 && right == 0 左节点有摄像头,右节点无覆盖
    // left == 0 && right == 1 左节点有无覆盖,右节点摄像头
    // left == 0 && right == 2 左节点无覆盖,右节点覆盖
    // left == 2 && right == 0 左节点覆盖,右节点无覆盖
    if (left == 0 || right == 0) {
        res++;
        return 1;
    }
    // 情况3
    // left == 1 && right == 2 左节点有摄像头,右节点有覆盖
    // left == 2 && right == 1 左节点有覆盖,右节点有摄像头
    // left == 1 && right == 1 左右节点都有摄像头
    // 其他情况前段代码均已覆盖
    if (left == 1 || right == 1) return 2;
    // 以上代码我没有使用else,主要是为了把各个分支条件展现出来,这样代码有助于读者理解
    // 这个 return -1 逻辑不会走到这里。
    return -1;
}

int minCameraCover(struct TreeNode* root) {
    res = 0;
    // 情况4
    if (traversal(root) == 0) {
        res++;
    }
    return res;
}

上一节:8、回溯算法相关

下一节:10、动态规划相关

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值