第一节 贪心法简介
贪心法的基本要素
- 最优子结构性质
- 这个与动态规划一样,即两个算法都具有最优子结构性质
- 但留意具体的表现形式不一样,动态规划为自下而上,贪心法则是自上向下的
- 贪心选择性质
- 正确性证明
留意贪心法的贪心选择性质是关键,我们首先要保证贪心选择得到的结果一定是最优的,因此在本章里每一道题目我们都要研究正确性证明
例题1 【力扣1833】雪糕的最大数量
题目描述 (注:本题与课本《4.3 最优装载》为同一道题的不同问法)
解题思路
这道题有一个有趣的坑,就是新手反而更容易想到用贪心法求解,而有一定基础尤其是学过动态规划的同学容易掉入思维定势中,一上来就尝试使用0-1背包的算法求解,虽然0-1背包算法能得到正确的答案,但其时间复杂度为O(nC),代码在力扣提交会得到超时的错误,这也提醒我们,要具体问题具体分析,不要一味依赖模板套路
我们首先先代码解决本题,本题的贪心选择性质为:每次从所有未购买的雪糕中选择一支价钱最便宜的进行购买,一直重复该贪心选择直到所有的雪糕都买完或者剩下的金钱买不起任何一支雪糕
算法实现
根据以上思想我们进一步设计算法,既然每次选最便宜的一支,那我们可以首先先对所有的雪糕数据按照价钱从小到大进行排序,然后依次购买即可。
代码实现(Java)
class Solution {
public int maxIceCream(int[] costs, int coins) {
// 按价钱从小到大进行排序
Arrays.sort(costs);
int n = 0; // 购买的总数量
for (int x : costs) {
if (coins >= x) {
coins -= x;
n++;
} else {
break;
}
}
return n;
}
}
正确性证明
以上都是不难理解的部分,下面我们讨论一个核心问题:为什么以上“选择最便宜的一支雪糕”的贪心选择是正确的?虽然好像从常理上不难理解,但是我们需要从数学严谨的角度加以证明,这里我们介绍一个比较容易套用在贪心法证明上的方法:反证法
猜想:设x0为当前价钱最便宜的一支雪糕,则必定存在一个最优解X={x0,x1,x2…x[k-1]},且X包含价钱最小的雪糕X
证明:用反证法,我们假设任何的最优解Y={y0,y1,y2…y[m-1]}均不包含x0,我们取其中一个解Y并取出当中的任意一支雪糕y0,由于x0<=y0,所以我们必定可以用x0替换y0,这样得到的最优解Y’={x0,y1,y2…y[m-1]}就包含了最便宜的雪糕x0,假设被推翻,证明成立
总结:反证法非常有用,接下来我们用类似的解法解决另一个例题
例题2 活动安排问题(书本4.1)
注:本题与力扣435. 无重叠区间为同一题的不同问法
- 最优子结构:问题是在一堆活动中选出数量最多的不冲突的活动,假如选择了活动x,然后我们在活动集合中移除x和与x时间冲突的活动,子问题便变为在剩下的活动中选出数量最多的不冲突的活动,于是问题转化为求子问题的最优解
- 贪心选择:我们在所有的活动中选择结束时间最早的活动x,然后进一步用同样的贪心选择求解剩下的子问题,直到没有更多的活动可以选择
- 正确性证明:跟例题1一样,我们尝试使用反证法证明贪心选择的正确性
猜想:设x0=[Lx0, Rx0]为结束时间最早的活动(即Rx0在所有Ri中最小),则必定存在最优解X = { x0, x1, x2, … x[k-1] } 且X包含x0
证明:反证法,假设存在更优解 Y = { y0, y1, y2… y[m-1] }
而且Y必定不包含x0。取Y中结束时间最小的区间y0。由于y0是y中结束时间最小的区间,也就是左边第一个区间,
又有x0的结束时间 <= y0的结束时间
所以我们必定可以用x0替换y0,得到Y’= { x0, y1, y2…y[m-1] }
从而假设的更优解同样包含x0与假设矛盾
总结
我们通过两道不同的题目得到贪心法的一般解题思路和过程:
- 确定最优子结构
- 分析题目,找到贪心选择策略
- 尝试证明贪心选择的正确性,如果能证明,则贪心法成立,反之,该题目不可用贪心法求解(例如第3章中的《打家劫舍》例题)