「算法刷题」贪心算法的相关经典题目
LeetCode题目 | 相关题目类型 | 相关链接 |
---|---|---|
455 | 分发饼干(简单难度) | 383. 赎金信 - 力扣(LeetCode) (leetcode-cn.com) |
为了了满足更多的小孩,就不要造成饼干尺寸的浪费。
大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的。
这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩。
可以尝试使用贪心策略,先将饼干数组和小孩数组排序。
然后从后向前遍历小孩数组,用大饼干优先满足
这个例子可以看出饼干9只有喂给胃口为7的小孩,这样才是整体最优解,并想不出反例,那么就可以撸代码了。
1 | // 时间复杂度:O(nlogn) |
从代码中可以看出我用了一个index来控制饼干数组的遍历,遍历饼干并没有再起一个for循环,而是采用自减的方式,这也是常用的技巧。
有的同学看到要遍历两个数组,就想到用两个for循环,那样逻辑其实就复杂了。
也可以换一个思路,小饼干先喂饱小胃口
代码如下:
1 | class Solution { |
LeetCode题目 | 相关题目类型 | 相关链接 |
---|---|---|
1005 | K次取反后最大化的数组和(简单难度) | 1005. K 次取反后最大化的数组和 - 力扣(LeetCode) (leetcode-cn.com) |
本题思路其实比较好想了,如何可以让数组和最大呢?
贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。
局部最优可以推出全局最优。
那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。
那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。
虽然这道题目大家做的时候,可能都不会去想什么贪心算法,一鼓作气,就AC了。
我这里其实是为了给大家展现出来 经常被大家忽略的贪心思路,这么一道简单题,就用了两次贪心!
那么本题的解题步骤为:
- 第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
- 第二步:从前向后遍历,遇到负数将其变为正数,同时K–
- 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
- 第四步:求和
对应C++代码如下:
1 | class Solution { |
LeetCode题目 | 相关题目类型 | 相关链接 |
---|---|---|
860 | 柠檬水找零(简单难度) | 860. 柠檬水找零 - 力扣(LeetCode) (leetcode-cn.com) |
本题只需维护三种金额的数量:5,10,20,
情况一:账单是5,直接收下。
情况二:账单是10,消耗一个5,增加一个10
情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
(收款20时,局部最优:优先消耗10和5,完成本次找零。少消耗5,因为5可以给10和20找零。全局最优:完成全部账单的找零。
局部最优可以推出全局最优,且找不到反例,就可以尝试贪心算法。
1 | class Solution { |
LeetCode题目 | 相关题目类型 | 相关链接 |
---|---|---|
53 | 最大子序和(简单难度) | 53. 最大子序和 - 力扣(LeetCode) (leetcode-cn.com) |
局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。
全局最优:选取最大“连续和”
遍历nums,从头开始用count累积,如果count一旦加上nums[i]变为负数,那么就应该从nums[i+1]开始从0累积count了,因为已经变为负数的count,只会拖累总和。
中间的最大和由result记录下来了。
题目中的答案有个示例【-2,-1】
if(result<count)和if(count<=0),写的时候我颠倒了,会改变了count的值,res就记录不到正确的最大值了。
1 | class Solution { |
LeetCode题目 | 相关题目类型 | 相关链接 |
---|---|---|
452 | 用最少数量的箭引爆气球(中等难度) | 452. 用最少数量的箭引爆气球 - 力扣(LeetCode) (leetcode-cn.com) |
直觉上来看,貌似只射重叠最多的气球,用的弓箭一定最少
试一下举反例,发现没有这种情况。
那么就试一试贪心吧!
局部最优:当气球出现重叠,一起射,所用弓箭最少。
全局最优:把所有气球射爆所用弓箭最少。
为了让气球尽可能的重叠,需要对数组进行排序。用到sort函数
这里的排序不是简单的对整数排序,是按照数组的第一元素从小到大排序,不能简单地用sort(nums.begin(),nums.end()),
而是用sort(nums.begin(),nums.end(),cmp),cmp函数必须加上static,返回值是bool类型。例如:
static bool cmp(vector
return a[0]<b[0];
}
传入参数时,如果是数组,用引用&不会另外开辟内存,而是直接用原数组的数进行运算。不用引用&,会为参数另外开辟内存,每次用cmp函数都要花时间开辟内存,可能会运行超出时间限制。
按照气球的起始位置排序,从前向后遍历气球数组,尽可能让气球重复,前两个区间:比较第一个的右边界>=第二个的左边界,则气球重叠。有两个已经重叠,再比较第三个时,要比较前两个区间的最小右边界和第三个区间的左边界。因为如果想三个都重叠,必须满足最小的右边界(画图可以看出)。
前两个重叠时,不用弓箭加一,因为刚开始默认弓箭为1(因为数组数>=1).不重叠,那肯定需要再有一个弓箭射后面的气球,这时弓箭数加一。
代码:
1 | class Solution { |
LeetCode题目 | 相关题目类型 | 相关链接 |
---|---|---|
435 | 无重叠区间(中等难度) | 435. 无重叠区间 - 力扣(LeetCode) (leetcode-cn.com) |
本题与i452的区别:
移除重叠区间,按照右边界从小到大排序,如果前一个的右边界大于下一个的左边界,则算是重叠,然后保留重叠两个区间的右边界最小值(较大的可能会重叠更多区间,题目要求是移除区间的最小数量)
cmp两种都可以,第二种,考虑了如果两个元素相等,再怎么判断。但a[0]<b[0];应该会对相等情况给一个结果应该。
1 | static bool cmp(vector<int>& a,vector<int>& b){ |
和
static bool cmp(vector<int>& a,vector<int>& b){
if(a[0]==b[0]){
return a[1]<b[1];
}
return a[0]<b[0];
}
1 | class Solution { |
LeetCode题目 | 相关题目类型 | 相关链接 |
---|---|---|
56 | 合并区间(中等难度) | 56. 合并区间 - 力扣(LeetCode) (leetcode-cn.com) |
1 | static bool cmp(vector<int> &a,vector<int> &b){ |
LeetCode题目* | 相关题目类型 | 相关链接 |
---|---|---|
55 | 跳跃游戏(中等难度) | 55. 跳跃游戏 - 力扣(LeetCode) (leetcode-cn.com) |
该题判断是否能跳到终点。
刚看到本题一开始可能想:当前位置元素如果是3,我究竟是跳一步呢,还是两步呢,还是三步呢,究竟跳几步才是最优呢?
其实跳几步无所谓,关键在于可跳的覆盖范围!不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围。
这个范围内,别管是怎么跳的,反正一定可以跳过来。
那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点!
每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围。
贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点。
1 | class Solution { |
题目 | 相关链接 |
---|---|
763划分字母区间(中等难度) |
记录每个字符在数组中出现的最远距离,找到之前遍历过的所有字母的最远边界,这个边界点就是分割点。前面出现的所有字母最远就到这个边界。
分为两步:
1 统计每个字符出现的最远位置
2 从头遍历字符,更新字符出现的最远下表,如果找到字符最远出现位置下表和当前下标相等,则找到了分割点。
1 | class Solution{ |
LeetCode题目* | 相关题目类型 | 相关链接 |
---|---|---|
135 | (中等难度) | 55. 跳跃游戏 - 力扣(LeetCode) (leetcode-cn.com) |
这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。
先确定右边评分大于左边的情况(也就是从前向后遍历)
此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
2.再确定左孩子大于右孩子的情况(从后向前遍历)
遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?
因为如果从前向后遍历,根据 ratings[i + 1] 来确定 ratings[i] 对应的糖果,那么每次都不能利用上前一次的比较结果了。
如果 ratings[i] > ratings[i + 1],此时candyVec[i](第i个小孩的糖果数量)就有两个选择了,一个是candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。
那么又要贪心了,局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量即大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。
整体代码:
1 | class Solution { |