进入贪心算法
基础理论
1、什么是贪心?
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
Eg:一堆钞票,你可以拿走 10 张,想达到最大金额,要怎么拿?
–》指定每一次拿最大的,每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。
2、什么时候用贪心?
没有固定套路,手动模拟能否推出最优解
最好用的策略就是举反例,如果想不到反例,那么就试一试贪心吧。
3、贪心解题步骤
贪心算法一般分为如下四步(理论上来说):
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
实际,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了
455.分发饼干
力扣题目链接(opens new window)
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 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:
- 输入: 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, g 两个数组都先进行排序。
题解
class Solution {
public:int findContentChildren(vector<int>& g, vector<int>& s) {int res=0;int index1=0;int index2=0;int m=g.size();int n=s.size();sort(g.begin(),g.end());sort(s.begin(),s.end());while(index1<m && index2<n){if(s[index2]>=g[index1]){res++;index1++;index2++;}else{index2++;}}return res;}
};
376. 摆动序列
力扣题目链接(opens new window)
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5]
是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5]
和 [1,7,4,5,5]
不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
示例 1:
- 输入:
[1,7,4,9,2,5]
- 输出: 6
- 解释: 整个序列均为摆动序列。
示例 2:
- 输入:
[1,17,5,10,13,15,10,5,16,8]
- 输出: 7
- 解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为
[1,17,10,13,10,16,8]
。
示例 3:
- 输入:
[1,2,3,4,5,6,7,8,9]
- 输出: 2
思考与分析
只要统计每次变化的次数,然后返回变化的次数+1,就是最后的结果了。由此完成题解 1。
用贪心完成:
- 局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值。
- 整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。
实际操作上,其实连删除的操作都不用做,因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量就可以了(相当于是删除单一坡度上的节点,然后统计长度)
这就是贪心所贪的地方,让峰值尽可能的保持峰值,然后删除单一坡度上的节点
在计算是否有峰值的时候,大家知道遍历的下标 i ,计算 prediff(nums[i] - nums[i-1])
和 curdiff(nums[i+1] - nums[i])
,如果prediff < 0 && curdiff > 0
或者 prediff > 0 && curdiff < 0
此时就有波动就需要统计。
这是我们思考本题的一个大体思路,但本题要考虑三种情况:
- 情况一:上下坡中有平坡
- 情况二:数组首尾两端
- 情况三:单调坡中有平坡
由此完成题解 2。
题解 1
class Solution {
public:int wiggleMaxLength(vector<int>& nums) {if(nums.size()==1) return 1;int direction,res=0;for(int i=1;i<nums.size();i++){if(nums[i] == nums[i-1])continue;else if(nums[i]>nums[i-1]){if(direction==1) continue;direction=1;res++;}else{if(direction==0) continue;direction=0;res++;}}return res+1;}
};
题解 2
class Solution {
public:int wiggleMaxLength(vector<int>& nums) {if (nums.size() <= 1) return nums.size();int curDiff = 0; // 当前一对差值int preDiff = 0; // 前一对差值int result = 1; // 记录峰值个数,序列默认序列最右边有一个峰值for (int i = 0; i < nums.size() - 1; i++) {curDiff = nums[i + 1] - nums[i];// 出现峰值if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {result++;preDiff = curDiff; // 注意这里,只在摆动变化的时候更新prediff}}return result;}
};
53. 最大子序和
力扣题目链接(opens new window)
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
- 输入:
[-2,1,-3,4,-1,2,1,-5,4]
- 输出: 6
- 解释: 连续子数组
[4,-1,2,1]
的和最大,为 6。
思考与分析
题目的目标是:找到一个具有最大和的连续子数组(子数组最少包含一个元素)。
所以肯定是从一个正数开始,从一个正数结束的。所以遇到正数是肯定要添加的。
所以,其实要看从一个正数到一个正数之前的和是否大于 0。
可以通过双指针,累计和<0了就不要了,由此完成题解 1
从贪心法来看:
局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。
全局最优:选取最大“连续和”
局部最优的情况下,并记录最大的“连续和”,可以推出全局最优。
从代码角度上来讲:遍历 nums,从头开始用 count 累积,如果 count 一旦加上 nums[i]
变为负数,那么就应该从 nums[i+1]
开始从 0 累积 count 了,因为已经变为负数的 count,只会拖累总和。
这相当于是暴力解法中的不断调整最大子序和区间的起始位置。
用 result 记录最大子序和区间和(变相的算是调整了终止位置)。由此完成题解 2.
题解 1:
class Solution {
public:int maxSubArray(vector<int>& nums) {int max=nums[0];int res=nums[0];for(int a=0,b=1;b<nums.size();b++){if(res<0){a=b;res=nums[b];}else{res+=nums[b];}if(res>max) max=res;}return max;}
};
题解 2
class Solution {
public:int maxSubArray(vector<int>& nums) {int result = INT32_MIN;int count = 0;for (int i = 0; i < nums.size(); i++) {count += nums[i];if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)result = count;}if (count <= 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和}return result;}
};