本文是基本常见的贪心算法题解
一、贪心算法简介 贪心无套路.
思考局部最优解是什么, 然后思考局部最优能不能推出全局最优(没有严格证明, 就是感觉局部最优可以推理出全局最优,同时自己还想不到反例, 那么就可以试一试贪心的策略, 仅仅是尝试)
贪心算法一般分为如下四步:
将问题分解为若干个子问题
找出适合的贪心策略
求解每一个子问题的最优解
将局部最优解堆叠成全局最优解
这个四步其实过于理论化了,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了.
贪心没有套路,说白了就是常识性推导加上举反例
二、贪心算法题解 小饼干先喂饱小胃口
大饼干先喂饱大胃口
这两种思路都可以
下面是leetcode的解法,我第一次写也是这样的.方便好理解.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : int findContentChildren (vector<int > &g, vector<int > &s) { int ret = 0 ; sort (g.begin (), g.end ()); sort (s.begin (), s.end ()); int n = g.size (), m = s.size (); int i = 0 , j = 0 ; while (i < n && j < m) { if (s[j] >= g[i]) { ret++; i++; j++; } else { j++; } } return ret; } };
这题的贪心方法
本题要考虑三种情况:
情况一:上下坡中有平坡
情况二:数组首尾两端
情况三:单调坡中有平坡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : int wiggleMaxLength (vector<int > &nums) { int n = nums.size (); if (n < 2 ) { return n; } int ret = 1 ; int prediff = 0 ; int curdiff = 0 ; for (int i = 0 ; i < n - 1 ; i++) { curdiff = nums[i + 1 ] - nums[i]; if ((curdiff > 0 && prediff <= 0 ) || (curdiff < 0 && prediff >= 0 )) { ret++; prediff = curdiff; } } return ret; } };
leetcode题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int wiggleMaxLength (vector<int > &nums) { int n = nums.size (); if (n < 2 ) { return n; } int prevdiff = nums[1 ] - nums[0 ]; int ret = prevdiff != 0 ? 2 : 1 ; for (int i = 1 ; i < n - 1 ; i++) { int curdiff = nums[i + 1 ] - nums[i]; if ((curdiff > 0 && prevdiff <= 0 ) || (curdiff < 0 && prevdiff >= 0 )) { ret++; prevdiff = curdiff; } } return ret; } };
或者这样写(都是一样的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int wiggleMaxLength (vector<int > &nums) { int n = nums.size (); if (n < 2 ) { return n; } int prevdiff = nums[1 ] - nums[0 ]; int ret = prevdiff != 0 ? 2 : 1 ; for (int i = 2 ; i < n; i++) { int curdiff = nums[i] - nums[i - 1 ]; if ((curdiff > 0 && prevdiff <= 0 ) || (curdiff < 0 && prevdiff >= 0 )) { ret++; prevdiff = curdiff; } } return ret; } };
暴力解法
超出了时间限制, 但是逻辑肯定是没问题的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int maxSubArray (vector<int > &nums) { int ret = INT_MIN; int n = nums.size (); for (int i = 0 ; i < n; i++) { int count = 0 ; for (int j = i; j < n; j++) { count += nums[j]; ret = ret > count ? ret : count; } } return ret; } };
贪心思路
贪心贪的是哪里呢?
如果 -2 1 在一起,计算起点的时候,一定是从 1 开始计算,因为负数只会拉低总和,这就是贪心贪的地方!
局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。
全局最优:选取最大“连续和”
局部最优的情况下,并记录最大的“连续和”,可以推出全局最优 。
从代码角度上来讲:遍历 nums,从头开始用 count 累积,如果 count 一旦加上 nums[i]变为负数,那么就应该从 nums[i+1]开始从 0 累积 count 了,因为已经变为负数的 count,只会拖累总和。
这相当于是暴力解法中的不断调整最大子序和区间的起始位置 。
那有问题了,区间终止位置不用调整么? 如何才能得到最大“连续和”呢?
区间的终止位置,其实就是如果 count 取到最大值了,及时记录下来了。例如如下代码:
1 if (count > result) result = count;
这样相当于是用 result 记录最大子序和区间和(变相的算是调整了终止位置) 。
当然题目没有说如果数组为空,应该返回什么,所以数组为空的话返回啥都可以了
贪心代码:
自己写的错误代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : int maxSubArray (vector<int > &nums) { int ret = INT_MIN; int n = nums.size (); int count = 0 ; for (int i = 0 ; i < n; i++) { count += nums[i]; if (count < 0 ) { count = 0 ; } if (ret < count) { ret = count; } } return ret; } };
错误原因如下.加上当前的nums[i]之后,应该先计算ret,然后判断加上当前的nums[i]之后的count与0的大小,然后判断要不要更新起始位置.
例如输入nums = [-1], 错误代码的输出就是0, 但是正确的预期结果却是-1
正确代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : int maxSubArray (vector<int > &nums) { int ret = INT_MIN; int count = 0 ; int n = nums.size (); for (int i = 0 ; i < n; i++) { count += nums[i]; if (ret < count) { ret = count; } if (count < 0 ) { count = 0 ; } } return ret; } };
常见误区
误区一:
思考的时候容易认为 如果输入用例都是-1,或者 都是负数,这个贪心算法跑出来的结果是 0, 这是又一次证明脑洞模拟不靠谱的经典案例 ,建议把代码运行一下试一试,就知道了,也会理解 为什么 result 要初始化为最小负数了。
误区二:
在使用贪心算法求解本题,经常陷入的误区,就是分不清,是遇到 负数就选择起始位置,还是连续和为负选择起始位置。
在动画演示用,可以发现, 4,遇到 -1 的时候,我们依然累加了,为什么呢?
因为和为 3,只要连续和还是正数就会 对后面的元素 起到增大总和的作用。 所以只要连续和为正数我们就保留。
这里也会有疑惑,那 4 + -1 之后 不就变小了吗? 会不会错过 4 成为最大连续和的可能性?
其实并不会,因为还有一个变量 result 一直在更新 最大的连续和,只要有更大的连续和出现,result 就更新了,那么 result 已经把 4 更新了,后面 连续和变成 3,也不会对最后结果有影响。
误区三:
也就是自己犯的错误.应该先更新ret, 再更新区间起始位置.因为ret完全有可能是负数, 当数组的元素都是负数的时候ret就一定是负数.
待更新……