一、基本概述
本质:
贪心的本质是选择每一段的局部最优,从而达到全局最优。
难点:
唯一的难点就是如何通过局部最优,推出整体最优。
做题方法:
毫无套路可言,最好用的策略就是多举几个测试用例,看一下贪心是否合适。
大致步骤:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
二、做题
第455题 分发饼干
一个数组表示 每个小孩对应的胃口
一个数组表示 每块饼干对应的尺寸
大部分情况下处理两个数组都会想到用两层 for 循环,但是本题中,可以只用一层 for 循环,
用 for 循环来遍历小孩对应的胃口,然后定义一个 index 变量 ,来控制饼干数组的遍历,这样可以大大的简化逻辑。
第376题 摆动序列
时间复杂度O(n),空间复杂度O(1)
这道题有点难了。。。
图示可以根据所给数组画折线图表示,折线图可以很清楚的看清整个过程的走势,
局部最优:删除单调坡度上的节点,但是单调坡度两端的节点不删,那么这个坡度就可以有两个局部峰值。
整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。
局部最优推出全局最优,并举不出反例,那么试试贪心算法。
贪心其实要从直觉出发,
比如目前要增加,那么我肯定是想把它增的高高的,这样随便随便再来一个数就会有比较大的概率是符合减的条件,
同理如果要减少,那么也是尽可能的降到足够的低,这样随便再来一个数就会有比较大的概率是符合增加的条件。
从上面两句话,来理解这个 贪 字,就是 大就足够的大 , 小就足够的小 , 是不是就是贪心啦~~~
这也就是贪心所贪的地方,让峰值尽可能的保持峰值,然后删除单一坡度上的节点1.
实际操作时,连删除的操作都不用做,因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量就可以。相当于是删除单一坡度上的节点,然后统计长度。
一个处理上的小技巧:
在同理峰值的时候,数组的最左面和最右面是不太容易来统计的,
因为整个统计的过程是根据统计相邻两数的差值计算,但是对于最左面的元素,没有前一个元素与其做差,也就没有差值
处理的方法就是,给最左面的元素的假定一个元素值(这个值等于最左面元素),这样就有了一个差值为0,这个处理有点类似于在处理链表问题时,设定一个 pre 节点。
第134题 加油站
法一:暴力解法
这道题的暴力解法也很巧妙,值得品味
首先:两层循环,第二层如何实现循环整个数组呢? 通过内层循环的起始位置设置为, j =( i + 1) % gas.size(),终止条件是 i != j
法二:贪心一
- 情况一:如果 gas 的总和小于 cost 总和,那么无论从哪里出发,一定是跑不了一圈的
- 情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。
- 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能把这个负数填平,能把这个负数填平的节点就是出发节点。
第714题 买卖股票
股票问题常常使用动态规划解决,但有时候贪心也是可以滴
所谓贪心 如何得到最大利润呢?
就是最低值买,最高值就卖,注意这里的最高值,表示算上手续费还可以盈利
要点:找到两个点:买入日期,卖出日期
- 买入日期:就是遇到更低的点就记录一下。
- 卖出日期:这个不太好确定,但是也没有必要算出准确的卖出日期,只要 [当前价格] 大于 [当前价格+手续费] ,就可以收获利润,至于准确的卖出日期,就是连续收获利润区间里的最后一天,(并不需要计算是具体的那一天)
所以在收获利润操作时,其实有三种情况:
- 情况一:收获利润的这一天并不是收获利润区间里的最后一天,也就是不卖出,后面继续收获利润
- 情况二:前一天是收获利润区间里的最后一天,也就是前一天卖出,今天要重新记录最小价格
- 情况三:保持原有状态,也就是不买不卖,不做任何操作
第968题 监控二叉树
从叶子节点开始找放置的位置,摄像头应该放在叶子节点的父节点的位置,一个摄像头可以相邻覆盖三层,所以隔两个节点放一个摄像头。
贪心思想
局部最优:让叶子节点的父节点安装摄像头,所用摄像头最少,
整体最优:全部摄像头数量最少。
每个节点可能具有的状态,分别对应一个数字:
- 0:该节点无无摄像头且无覆盖
- 1:该节点有摄像头
- 2:该节点无摄像头且有覆盖
对于遍历的过程中遇到的空节点属于什么状态呢? 用假设法试一下~
- 若为状态0,这样叶子节点就需要放摄像头了
- 若为状态1,这样叶子节点的父节点就没有必要放摄像头了,而是可以把摄像头放在叶子节点的爷爷节点上了
- 因此只能是状态2,这样就可以保证在叶子节点的父节点放置摄像头。
对于单个节点可能有的情况可分为以下四类:
- 情况1:左右孩子都有覆盖,那么此时该节点应该就是无覆盖的状态了,也就是状态0
- 情况2:左右孩子至少有一个无覆盖的情况,则该节点应该放置一个摄像头,也就是状态1
- left == 0 && right == 0
- left == 1 && right == 0
- left == 2 && right == 0
- left == 0 && right == 1
- left == 0 && right == 2
- 情况3:左右孩子至少有一个有摄像头,那么本节点无需放置摄像头,且已经是被覆盖的状态,也就是状态2
- left == 1 && right == 1
- left == 1 && right == 2
- left == 2 && right == 1
- 情况4:头结点没有覆盖
- 以上三种情况都不涵盖对头结点的特殊处理,当递归结束后,头结点还有可能是 无覆盖的状态,
- 因此递归结束后,需要单独判断一下头结点,如果没有被覆盖,则 result++;