贪心和动态规划的区别
- 有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿? 【贪心】
-- 指定每次拿最大的,最终结果就是拿走最大数额的钱。(每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优) - 有一堆盒子,你有一个背包体积为n,如何把背包尽可能装满?
-- 如果还每次选最大的盒子,就不行了。这时候就需要动态规划
贪心的套路
- 通过局部最优,推出整体最优
- 靠自己手动模拟,如果模拟可行,就可以试一试贪心策略,如果不可行,可能需要动态规划。
- 举反例,如果想不到反例,那么就试一试贪心吧
- 贪心有时候就是常识性的推导,所以会认为本应该就这么做
贪心的解题步骤
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
贪心的题目
1. 分发饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;
每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。
你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
【思路】为了满足更多的小孩,就不要造成饼干尺寸的浪费。大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的。这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩。
- 先将饼干数组和小孩数组排序。
- 从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量
2. 摆动序列
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
【思路】需要画图
- 局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰
- 整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。
让峰值尽可能的保持峰值,然后删除单一坡度上的节点
另外要考虑特殊情况 - 上下坡中有平坡
- 数组首尾两端
- 单调坡度有平坡
3. 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
【思路】如果 -2 1 在一起,计算起点的时候,一定是从1开始计算,因为负数只会拉低总和,这就是贪心贪的地方!
- 局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。
- 全局最优:选取最大“连续和”
4. 买卖股票的最佳时机II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易
【思路】这道题目可能我们只会想,选一个低的买入,再选个高的卖,再选一个低的买入.....循环反复
其实最终利润是可以分解的。假如第0天买入,第3天卖出,那么利润为:prices[3] - prices[0]。
相当于(prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])。此时就是把利润分解为每天为单位的维度,而不是从0天到第3天整体去考虑
- 局部最优:收集每天的正利润
- 全局最优:求得最大利润。
5. 跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
【思路】其实跳几步无所谓,关键在于可跳的覆盖范围,那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点!
每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围。
- 贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围)
- 整体最优解:最后得到整体最大覆盖范围,看是否能到终点。
6. 跳跃游戏II
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。
【思路】本题要计算最小步数,那么就要想清楚什么时候步数才一定要加一
- 局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一
- 整体最优:一步尽可能多走,从而达到最小步数。
从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最小步数。
这里需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖。
7. K次取反后最大化的数组和
给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)以这种方式修改数组后,返回数组可能的最大和。
【思路】
- 局部最优:让绝对值大的负数变为正数,当前数值达到最大
- 整体最优:整个数组和达到最大。
解决步骤:
第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
第二步:从前向后遍历,遇到负数将其变为正数,同时K--
第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
第四步:求和
8. 加油站
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
【思路】
1.可以双层循环,使用暴力看是否能得到一个解
2. 使用贪心,分析思路如下:
- 局部最优:当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行
- 全局最优:找到可以跑一圈的起始位置
首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。每个加油站的剩余量rest[i]为gas[i] - cost[i],i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油。起始位置从i+1算起,再从0计算curSum
9. 分发糖果
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
每个孩子至少分配到 1 个糖果。相邻的孩子中,评分高的孩子必须获得更多的糖果。
【思路】这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。
先确定右边评分大于左边的情况(也就是从前向后遍历):
- 局部最优:只要右边评分比左边大,右边的孩子就多一个糖果
- 全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
再确定左孩子大于右孩子的情况(从后向前遍历) - 局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量既大于左边的也大于右边的
- 全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。
10. 柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
【思路】只需要维护三种金额的数量,5,10和20。有三种情况:
情况一:账单是5,直接收下。
情况二:账单是10,消耗一个5,增加一个10
情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
- 局部最优:遇到账单20,优先消耗美元10,完成本次找零
- 全局最优:完成全部账单的找零
11. 根据身高重建队列
假设有打乱顺序的一群人站成一个队列,数组 people
people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人
请你返回正确顺序的数组 people 所表示的队列
【思路】首先需要根据体重排序,找到其对应的顺序,再更具k值将人放在对应的位置
遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。
按照身高排序之后,优先按身高高的people的k来插入,后序插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列。
- 局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性
- 全局最优:最后都做完插入操作,整个队列满足题目队列属性
12. 用最少数量的箭引爆气球
给你一个数组 points ,其中 points [i] = [xstart,xend] ,弓箭可以从x轴垂直出发,返回引爆所有气球所必须射出的最小弓箭数。
【思路】只射重叠最多的气球,用的弓箭一定最少
- 局部最优:当气球出现重叠,一起射,所用弓箭最少
- 全局最优:把所有气球射爆所用弓箭最少。
为了让气球尽可能的重叠,需要对数组进行排序,如果气球重叠了,重叠气球中右边边界的最小值 之前的区间一定需要一个弓箭。
这里其实是需要代码功底的,那代码功底怎么练?
多看多写多总结!
13. 无重叠区间
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
【思路】让区间尽可能的重叠。按照右边界排序,从左向右记录非交叉区间的个数。最后用区间总数减去非交叉区间的个数就是需要移除的区间个数了。
14. 划分字母区间
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
【思路】要求同一字母最多出现在一个片段中,那么如何把同一个字母的都圈在同一个区间里呢?
在遍历的过程中相当于是要找每一个字母的边界,如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了。此时前面出现过所有字母,最远也就到这个边界了。
- 统计每一个字符最后出现的位置
- 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点
【其他转换思路】统计字符串中所有字符的起始和结束位置,记录这些区间(实际上也就是无重叠区间题目里的输入),将区间按左边界从小到大排序,找到边界将区间划分成组,互不重叠。找到的边界就是答案。
15. 合并区间
给出一个区间的集合,请合并所有重叠的区间。
【思路】先排序,让所有的相邻区间尽可能的重叠在一起,按左边界,或者右边界排序都可以,处理逻辑稍有不同。
- 按照左边界从小到大排序之后,如果 intervals[i][0] <= intervals[i - 1][1] 即intervals[i]的左边界 <= intervals[i - 1]的右边界,则一定有重叠
- 合并区间:用左边界和右边界作为合并后的区间
16. 单调递增的数字
给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。
【思路】
- 98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]--,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数。
- 从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299
17. 监控二叉树
给定一个二叉树,我们在树的节点上安装摄像头。节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。计算监控树的所有节点所需的最小摄像头数量。
【思路】首先要想,如何放置,才能让摄像头最小的呢?
- 从题目中示例,其实可以得到启发,我们发现题目示例中的摄像头都没有放在叶子节点上!(摄像头可以覆盖上中下三层,如果把摄像头放在叶子节点上,就浪费的一层的覆盖。)
- 头结点放不放摄像头也就省下一个摄像头, 叶子节点放不放摄像头省下了的摄像头数量是指数阶别的。所以从叶子节点开始看。
- 局部最优:让叶子节点的父节点安摄像头,所用摄像头最少
- 整体最优:全部摄像头数量所用最少!