贪心算法是一种在每一步选择中都采用在当前状态下最好或最优(即最有利)的选择, 从而希望导致结果是全局最好或者
最优的算法.(用贪心算法,尤其是最基础的贪心,每次当下情况下找到最优不一定能够达到全局最优的情况)
贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退.动态规划会保存以前的运算结果, 并根据以前的结果对当前进行选择, 有回退功能.
对比:
贪心: 当下做局部最优判断
回溯: 能够回退
动态规划: 最优判断 + 回退 (带最优判断的回溯,我们叫做动态规划,动态规划会保存以前的运算结果, 并根据以前保存的结果, 对当前进行选择有回退功能)
贪心法可以解决一些最优化问题, 如: 求图中的最小生成树,求哈夫曼编码等. 然而对于工程和生活中的问题, 贪心法一般不能得到我们想要的答案, 一旦一个问题可以通过贪心法来解决,那么贪心法一般是解决这个问题的最好办法.由于贪心法的高效性以及其所求得的答案比较接近最优结果, 贪心法也可以用作辅助算法,或者解决一些要求结果不特别精确的问题
应用实例 零钱兑换
/*
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1: 输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2: 输入: coins = [2], amount = 3
输出: -1
说明: 你可以认为每种硬币的数量是无限的。
*/
当硬币可选集合固定: Coins = [20, 10, 5, 1]
求最少可以几个硬币拼出总数, 比如total = 36
首先用20先去匹配, 20最多可以匹配多少个,我们就把20能匹配的个数全部都用总数减去,现在总数为36,所以总数为1个,也就是36除以20,整除除得多少表示20可以用多少个,这里只有一个,那么得到16, 再看10, 在看5, 在看1, 所以这里贪心法是先用最大的去匹配, 再用次大的再匹配
为什么可以用贪心法,是因为硬币的话20 10 5 1前面的硬币依次是后面这些硬币的倍数, 所以如果你需要用两个10,或者4个5的话,肯定还不如用一个20,因为后面都是整除前面最大的硬币,贪心法每次用最大的即可, 既然用20,那么肯定后面这些会不优于直接选20的情况, 贪心法有它的特殊性, 这个硬币有整除的关系,所以用贪心法, 但在大部分情况下
适用贪心算法的场景
简单地说, 问题能够分解成子问题来解决, 子问题的最优解能递推到最终问题的最优解,这种子问题最优解称为最优子结构.
贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退.动态规划则会保存以前的运算结果, 并根据以前结果对当前进行选择, 有回退功能
在现实生活中很少用到贪心, 你要是每天只满足自己当下的需求, 你觉得游戏好就打游戏,或者饿了饱吃一顿, 每天都吃很多类似于这样的东西, 最后发现长期是达不到最优解的,比如时间都浪费在一些可能提高自己的短期比较痛苦但长期的话,对你有益的事情上面,以及要自律,保持体重,或者是要去健身这些, 这些都是短期让你痛苦,长期让你受益的行为
分发饼干 思想 先排序, 排序之后从小到大匹配两个升序的数组
# 伪代码 时间复杂度为O(N), N表示小孩胃口和饼干长度较小者
res = 0;
q.sort()
s.sort()
g_length = len(g)
s_length = len(s)
i = 0
j = 0
while i < g_length and j < s_length:
if g[i] <= s[j]:
#可以满足胃口, 把小饼干喂给小朋友
res += 1
i += 1
j += 1
else:
# 不满足胃口, 查看下一块小饼干
j += 1
return res
最佳买卖股票时期 输入[7, 1, 5, 3, 6 ,4] 输出7, 在1这买入,在5这卖出,赚4元, 然后在3买入,在6卖出,赚3元 , 4+3 = 7这就是所谓的结果, 输入[1, 2, 3, 4, 5]在第一天买入, 一直是前一天买后一天抛直到最后,赚4元.所以这个思路就是只要后一天比前一天大,我们就在前一天买入,后一天抛
// 一
public int maxProfit(int[] prices) {
int maxProfit = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] > prices[i - 1]) {
maxProfit += prices[i] - prices[i - 1];
}
}
return maxProfit;
}
// 二
public int maxProfit(int[] prices) {
int maxProfit = 0;
for (int i = 0; i < prices.length - 1; i++) {
if (prices[i + 1] > prices[i]) {
maxProfit += prices[i + 1] - prices[i];
}
}
return maxProfit;
}
跳跃游戏 从后往前贪心
// 例如输入为 [2, 3, 1, 1, 4] 或者输入为[3, 2, 1, 0, 4]
public boolean canJump(int[] nums) {
if (nums == null) {
return false;
}
int endReachable = nums.length - 1;
for (int i = nums.length - 1; i >= 0; i--) {
// 表示从i这个点最多可以跳多远, 如果它最多可以跳的距离大于最后的一个能够跳到最终点的下标的话, 说明i的话是可以跳到最后点的, 那么就把i更新到endReachable, endReachable始终指定是最前面一个下标它能够跳到最后点, 那么这个从最后往前不断进行循环,且每时候都检查,如果当前的i能够跳到最后的话, 那么则把i更新到endReachable, 最后判断endReachable是不是为0, 是0的话表示第一个坐标的位置也可以跳到最后, 为什么这用到贪心,是因为只要记能够跳到最后的那个位置的第一个值就是记这么一个最前者的值, 就节省了一个数组来记录重要的结果, 当然也可以节省一层循环
if (nums[i] + i >= endReachable) {
endReachable = i;
}
}
return endReachable == 0;
}