贪心算法解题框架
一句话总结
回溯算法的剪枝优化是提前排除不可能的答案,使树结构尽可能小,最终的算法复杂度一般是指数级别;动态规划的备忘录优化是为了避免重复计算,把树形结构优化成线性结构, 最终的算法复杂度一般是多项式级别。
上述两种优化只是减少了无效枚举,但依然都枚举了所有可行解,从而试图寻找最优的那个解。
贪心算法和它们的区别是:有些问题其实不需要完整地枚举所有可行解,就可以推导出最优解。这样一来,进一步减少了枚举空间,效率自然会更高。
举个简单的例子,就能直观地展现贪心算法了。
问题一
比方说现在有两种钞票,面额分别为 1 元和 100 元,每种钞票的数量无限,但现在你只能选择 10 张,请问你应该如何选择,才能使得总金额最大?
那你肯定会说,肯定是 10 张全拿 100 元的钞票,共计 1000 元,这就是最优策略。
但按照算法思维,这个问题的本质是做 10 次选择,每次选择有两种可能,分别是 1 元和 100 元,一共有 种可能的选择。
所以你心里首先应该出现一棵高度为 10 的二叉树来枚举所有可行解,遍历这 些可行解,然后可以得到最优解。
心里只要有这样一棵二叉树,就应该能写出代码:
// 定义:做 n 次选择,返回可以获得的最大金额
int findMax(int n) {
if (n == 0) return 0;
// 这次选择 1 元,然后递归求解剩下的 n - 1 次选择的最大值
int result1 = 1 + findMax(n - 1);
// 这次选择 100 元,然后递归求解剩下的 n - 1 次选择的最大值
int result2 = 100 + findMax(n - 1);
// 返回两种选择中的最大值
return max(result1, result2);
}
这个算法的复杂度是二叉树的节点数量,是指数级别,非常高。不过到这里你应该已经看出来了,findMax(n - 1) 的值肯定都一样,那么 100 + findMax(n - 1) 必然大于 1 + findMax(n - 1),因此可以进行优化:
// 优化一、没必要对两种选择进行比较了
int findMax(int n) {
if (n == 0) return 0;
int result = 100 + findMax(n - 1);
return result;
}
// 优化二、递归改为迭代
int findMax(int n) {
int result = 0;
for (int i = 0; i < n; i++) {
result += 100;
}
return result;
}
// 优化三、直接计算结果就行了
int findMax(int n) {
return 100 * n;
}
这就是贪心算法,复杂度从 优化到了 ,堪称离谱。
其实算法本来就很简单,就是枚举,是不是很简单? 围绕枚举,衍生出各种优化方法,起了些名字,其实从原理上讲,没多大差别,不过是见招拆招罢了。
贪心选择性质
首先来举例说明什么是贪心选择性质。
开头举例的这个最大金额的问题符合贪心选择性质,所以我们可以用贪心算法,把 的暴力枚举算法直接优化到了 。
问题二
作为对比,我们稍微改一改问题一:
现在有两种钞票,面额分别为 1 元和 100 元,每种钞票的数量无限。现在给你一个目标金额
amount,请问你最少需要多少张钞票才能凑出这个金额?
这道题其实就是动态规划中讲解的凑零钱问题。
我们是如何解决问题二的?首先也是抽象出递归树,写出指数级别的暴力枚举算法,然后发现了重叠子问题,于是用备忘录消除重叠子问题,这就是标准的动态规划算法的求解过程,不能再优化了。
所以,问题二和问题一到底有什 么区别?区别在于,问题二没有贪心选择性质,而问题一有。
贪心选择性质
贪心选择性质就是说能够通过局部最优解直接推导出全局最优解。
结合案例来说,对于问题一,局部最优解就是每次都选择 100 元,因为 100 > 1;对于问题二,局部最优解也是每次都选择 100 元,因为每张面额尽可能大,所需的钞票数量就能尽可能少。
但区别在于,问题一中每一次选择的局部最优解组合起来就是全局最优解,而问题二中不是。
比方说目标金额 amount = 3,虽然每次选择 100 元是局部最优解,但想凑出 3 元,只能选择 3 张 1 元,局部最优解不一定能构成全局最优解。
对于问题二的场景,不符合贪心选择性质,所以不能用贪心算法,只能枚举所有可行解,才能计算出最优解。
贪心选择性质 vs 最优子结构
动态规划中提到,算法问题必须要有「最优子结构」性质,才能通过子问题的最优解推导出全局最优解,这是动态规划算法的基础。
那么这个「贪心选择性质」和「最优子结构」有什么区别?
- 最优子结构的意思是说,现在我已经把所有子问题的最优解都求出来了,然后我可以基于这些子问题的最优解推导出原问题的最优解。
- 贪心选择性质