「贪心的本质是选择每一阶段的局部最优,从而达到全局最优」
贪心无套路
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; }