代码随想录算法训练营第二十六天
贪心算法理论基础
视频讲解: 贪心算法理论基础!
什么是贪心
其本质是每一阶段的局部最优解,从而达到全局的最优解。
贪心的套路(方法论)
贪心算法没有固定的套路,其核心就是通过局部最优,推导出整体最优。
贪心算法的一般解题步骤
分为四步:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
LeetCode 455.分发饼干
题目链接:455.分发饼干
文章讲解:代码随想录#455.分发饼干
视频讲解:贪心算法,你想先喂哪个小孩?| LeetCode:455.分发饼干
题目描述
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例1
输入:g = [1,2,3], s = [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
示例2
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例3
输入: g = [1,2], s = [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.
提示
- 1 <= g.length <= 3 * 10^4
- 0 <= s.length <= 3 * 10^4
- 1 <= g[i], s[j] <= 2^31 - 1
思路
目的是求出满足孩子胃口的最大数,就像田忌赛马一样,需要用最大饼干去喂最大胃口,如果胃口过大,需要查找次大的胃口,直到找到满足要求的胃口,如果胃口合适(s[j] >= g[i]),则总数sum加1,然后用次大的饼干继续喂胃口,直到遍历结束。
如果胃口太大了,找不到合适的饼干,说明没有饼干能喂这样的胃口,此时应该放弃这样的喂口,转而寻找较小的胃口。在整个过程中,用for循环遍历胃口,只有找到合适的饼干后,才去找较小的饼干的。
盗图说明一下,比较形象
在遍历前需要对饼干和胃口进行排序。
参考代码
int cmp(const void *p1, const void *p2)
{
return *(int*)p1 - *(int*)p2;
}
int findContentChildren(int* g, int gSize, int* s, int sSize) {
if(sSize == 0)
return 0;
int sum = 0;
int cook = sSize - 1;
qsort(g, gSize, sizeof(int), cmp); // 对胃口进行排序
qsort(s, sSize, sizeof(int), cmp); // 对饼干进行排序
for (int i = gSize - 1; i >= 0; i--) {
if (cook >= 0 && s[cook] >= g[i]) {
sum++;
cook--;
}
}
return sum;
}
LeetCode 376. 摆动序列
题目链接:376. 摆动序列
文章讲解:代码随想录#376. 摆动序列
视频讲解:贪心算法,寻找摆动有细节!| LeetCode:376.摆动序列
题目描述
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。
示例1
输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。
示例2
输入:nums = [1,17,5,10,13,15,10,5,16,8]
输出:7
解释:这个序列包含几个长度为 7 摆动序列。
其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8) 。
提示
- 1 <= nums.length <= 1000
- 0 <= nums[i] <= 1000
思路
这道题的思路可以看看卡哥的随想录,讲得比较清楚,下面我就简单地做下笔记。
先上图解释一下这道题
贪心算法的本质就是通过局部最优来推出整体最优。
局部最优:对于一个单调坡度的节点来说,它有两个局部峰值,比如图中的1和17这个坡度来说,1和17就是两个峰值。
整体最优:整个序列有最多的局部峰值,从面达到最长的摆动序列
对于节点i来说,要计算是否有峰值,需要计算这个点与前一个点的差值prediff(nums[i] - nums [i - 1]) 和后一个点g与这个点的差值curdiff(num[i + 1] - nums[i])。
如果prediif < 0 && curdiff > 0 或者 prediff > 0 && curdiff < 0,说明此时的点就是一个峰值。
还需要考虑以下三种情况:
① 上下波中有平坡
② 数组首尾两端
③ 单调坡中有平坡
思路后面再详细补充…
参考代码
int wiggleMaxLength(int* nums, int numsSize){
if (numsSize <= 1) return numsSize;
int len = 1;
int preDiff , curDiff;
preDiff = curDiff = 0;
for(int i = 0; i < numsSize - 1; ++i) {
curDiff = nums[i+1] - nums[i];
if((curDiff > 0 && preDiff <= 0) || (preDiff >= 0 && curDiff < 0)) {
preDiff = curDiff;
len++;
}
}
return len;
}
LeetCode 53. 最大子序和
题目链接:53. 最大子序和
文章讲解:代码随想录#53. 最大子序和
视频讲解:贪心算法的巧妙需要慢慢体会!LeetCode:53. 最大子序和
题目描述
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例1
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例2
输入:nums = [1]
输出:1
提示
- 1 <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
思路
使用贪心算法时关键点是,如果连续和为负数时要立即放弃当前的数,从下一个数元素重新开始计算,这样就可以得到全局最优。
参考代码
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int maxSubArray(int* nums, int numsSize) {
int max = -100000;
int sum = 0;
for (int i = 0; i < numsSize; i++) {
sum += nums[i];
if (sum <0) sum = 0;
max = MAX(sum, max);
}
return max;
}