1、什么是贪心算法(Greedy)
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。实际上,并不总能得到全局最优解。
**经典的应用场景:**霍夫曼编码(Huffman Coding)、Prim 和 Kruskal 最小生成树算法、还有 Dijkstra 单源最短路径算法。
**适用的场景:**简单地说,问题能够分解成子问题来解决,子问题的最优解能地推到最终问题的最优解。这种子问题最优解成为最优子结构。
**与动态规划的区别:**在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。
2、贪心算法解决问题的步骤
**第一步,当我们看到这类问题的时候,首先要联想到贪心算法:**针对这一组数据,我们定义了限制值和期望值,希望从中选出几个数据,在满足限制值的情况下,期望值最大。
**第二步当我们尝试看下这个问题是否可以用贪心算法解决:**每次选择当前情况下,在对限制值同等贡献量的情况下,对期望值贡献最大的数据。
**第三步,我们举几个例子看下贪心算法产生的结果是否是最优的。**大部分情况下,举几个例子验证一下就可以了。严格地证明贪心算法的正确性,是非常复杂的,需要涉及比较多的数学推理。而且,从实践的角度来说,大部分能用贪心算法解决的问题,贪心算法的正确性都是显而易见的,也不需要严格的数学推理。
3、实战分析
3.1、零钱兑换
class Solution {
int ans;
public int coinChange(int[] coins, int amount) {
Arrays.sort(coins);
int max = amount / coins[0] + 1;
ans = max;
coinChange(coins.length - 1, coins, 0, amount);
return ans == max ? -1 : ans;
}
// index-硬币索引
// coins-硬币
// count-需要的硬币数量
// amount-剩余的零钱大小
private void coinChange(int index, int[] coins, int count, int amount) {
if (amount == 0) {
ans = Math.min(count, ans);
return;
}
if (index < 0) {
return;
}
// 需要 k 个 coins[index] 大小的硬币
int k = amount / coins[index];
// count + k < ans 表示还未找到最优解,继续查找
for (; k >= 0 && count + k < ans; k--) {
coinChange(index - 1, coins, count + k, amount - k * coins[index]);
}
}
}
3.2、买卖股票的最佳时机 II
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) {
return 0;
}
int profit = 0;
for (int i = 1; i < prices.length; i++) {
profit += Math.max(0, prices[i] - prices[i - 1]);
}
return profit;
}
}
4、内容小结
贪心法适用的场景比较有限,它可以解决一些最优化问题,如:最小生成树、单源最短路径算法、Huffman coding 等。然而对于工程和生活中的问题,贪心法一般不能得到我们所要求的的答案。
一旦一个问题可以通过贪心法来解决,那么贪心法一般是解决这个问题的最好办法。由于贪心法的高效性以及其所求得的答案比较接近最优结果,贪心法也可以用作辅助算法或者直接解决一些要求结果不特别精确的问题
贪心算法的最难的一块是如何将要解决的问题抽象成贪心算法模型,只要这一步搞定之后,贪心算法的编码一般都很简单。贪心算法解决问题的正确性虽然很多时候都看起来是显而易见的,但是要严谨地证明算法能够得到最优解,并不是件容易的事。所以,很多时候,我们只需要多举几个例子,看一下贪心算法的解决方案是否真的能得到最优解就可以了。