文章目录
- 贪心算法
- 1、概念
- 2、题目
- leetcode 455. 分发饼干
- leetcode 376. 摆动序列
- leetcode 53. 最大子序和
- leetcode 122. 买卖股票的最佳时机 II
- leetcode 55. 跳跃游戏
- leetcode 45. 跳跃游戏 II
- leetcode 1005. K 次取反后最大化的数组和
- leetcode 134. 加油站
- leetcode 135. 分发糖果
- leetcode 860. 柠檬水找零
- leetcode 406. 根据身高重建队列
- leetcode 452. 用最少数量的箭引爆气球
- leetcode 435. 无重叠区间
- leetcode 763. 划分字母区间
- leetcode 56. 合并区间
- leetcode 738. 单调递增的数字
- leetcode 714. 买卖股票的最佳时机含手续费
- leetcode 968. 监控二叉树
贪心算法
1、概念
什么是贪心
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
这么说有点抽象,来举一个例子:
例如,有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?
指定每次拿最大的,最终结果就是拿走最大数额的钱。
每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。
贪心的套路(什么时候用贪心)
说实话贪心算法并没有固定的套路。
所以唯一的难点就是如何通过局部最优,推出整体最优。
那么如何能看出局部最优是否能推出整体最优呢?有没有什么固定策略或者套路呢?
不好意思,也没有! 靠自己手动模拟,如果模拟可行,就可以试一试贪心策略,如果不可行,可能需要动态规划。
有同学问了如何验证可不可以用贪心算法呢?
最好用的策略就是举反例,如果想不到反例,那么就试一试贪心吧。
贪心一般解题步骤
贪心算法一般分为如下四步:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
其实这个分的有点细了,真正做题的时候很难分出这么详细的解题步骤,可能就是因为贪心的题目往往还和其他方面的知识混在一起。
2、题目
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. 最大子序和
- 局部最优就是当前元素之和大于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
- 局部最优就是收集每天的正利润
- 全局最优就是将所有正利润相加
代码如下:
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. 跳跃游戏
- 局部最优就是每次记录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
- 局部最优,每次都要走到当前距离的最后
- 当前距离走满之后,跳跃步数 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 次取反后最大化的数组和
- 局部最优,将绝对值大的负数取反,剩余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. 分发糖果
- 先考虑右边孩子 大于 左边孩子 (从前向后遍历)
- 再考虑左边孩子 大于 右边孩子 (从后向前遍历,这样才能使用前一次遍历的结果)
- 每一次遍历的局部最优都是确定一个方向上的条件满足
- 全局最优就是两个方向都满足条件时,要发的糖果数量
代码如下:
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. 根据身高重建队列
- 局部最优:优先按身高高的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. 用最少数量的箭引爆气球
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. 划分字母区间
此题将分割字符串转化为查找元素最远下标;
- 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. 合并区间
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. 买卖股票的最佳时机含手续费
如果使用贪心策略,就是最低值买,最高值(如果算上手续费还盈利)就卖。
此时无非就是要找到两个点,买入日期,和卖出日期。
买入日期:其实很好想,遇到更低点就记录一下。
卖出日期:这个就不好算了,但也没有必要算出准确的卖出日期,只要当前价格大于(最低价格+手续费),
就可以收获利润,至于准确的卖出日期,就是连续收获利润区间里的最后一天(并不需要计算是具体哪一天)。
所以我们在做收获利润操作的时候其实有三种情况:
情况一:收获利润的这一天并不是收获利润区间里的最后一天(不是真正的卖出,相当于持有股票),所以后面要继续收获利润。
情况二:前一天是收获利润区间里的最后一天(相当于真正的卖出了),今天要重新记录最小价格了。
情况三:不作操作,保持原有状态(买入,卖出,不买不卖)
代码如下:
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;
}