贪心算法
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
自顶向下和自底向上
链接:自顶向下&自底向上
自顶向下大概意思是从抽象到具体
贪心算法性质
理解每种算法的性质和特征,才能帮助我们理解算法实现的原理。对于初学算法的同学而言,不能很好的理解算法的性质,就很难继续深入地理解掌握算法如何应用,只知大概的算法思想,在解决问题时任然一筹莫展,我个人的理解是用一些算法例题来验证算法的性质,通过对应关系,达到对该算法的初步全面理解,虽然效率有点低,但是理解万岁。
-
贪心选择性质
是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。
贪心算法通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。
对于一个具体问题,要确定它是否具有贪心选择性质,做出贪心选择后,原问题简化为规模更小的类似子问题。必须证明每一步所作的贪心选择最终导致问题的整体最优解,这就要看下面的最优子结构性质了。 -
最优子结构性质
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。水平有限,数学证明就不阐述了。 (出现了贪心选择与动态规划的交集)
例题
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 3 * 10 ^ 4
0 <= prices[i] <= 10 ^ 4
来源:力扣(LeetCode)
解题
“贪心算法” 和 “动态规划”、“回溯搜索” 算法一样,完成一件事情,是分步决策的;
“贪心算法” 在每一步总是做出在当前看来最好的选择,我是这样理解 “最好” 这两个字的意思:
“最好” 的意思往往根据题目而来,可能是 “最小”,也可能是 “最大”;
贪心算法和动态规划相比,它既不看前面(也就是说它不需要从前面的状态转移过来),也不看后面(无后效性,后面的选择不会对前面的选择有影响),因此贪心算法时间复杂度一般是线性的,空间复杂度是常数级别的。
这道题 “贪心” 的地方在于,对于差值“今天的股价-昨天的股价”,得到的结果有 3 种可能:(1)正数(2)0(3)负数。
贪心算法的决策是:只加正数。
本题算法流程
从第 i 天(这里 i >= 1)开始,与第 i - 1 的股价进行比较,如果股价有上升(严格上 升),就将升高的股价( prices[i] - prices[i- 1] )记入总利润,按照这种算法,得到的结果 就是符合题意的最大利润。
关于算法描述解释
该算法仅可以用于计算,但计算的过程并不是真正交易的过程,但可以用贪心算法计算题目要求的最大利润。下面说明这个等价性:以 [1, 2, 3, 4] 为例,这 4 天的股价依次上升,按照贪心算法,得到的最大利润是:
res = (prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])
= prices[3] - prices[0]
仔细观察上面的式子,按照贪心算法,在索引为 1、2、3 的这三天,我们做的操作应该是买进昨天的,卖出今天的,虽然这种操作题目并不允许,但是它等价于:“在索引为 0 的那一天买入,在索引为 3 的那一天卖出”。
class Solution {
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;
}
}
复杂度分析:
时间复杂度:O(N),这里 N 表示股价数组的长度;
空间复杂度:O(1)。
贪心算法验证
我们借助 “差分” 这个概念,可以证明 “贪心算法” 的有效性。贪心算法就是选择那些所有差分(严格)大于 0 的数,把它们相加即可。
使用反证法:假设 “贪心算法” 得到的解并不是最优解,即我们还能够找到一个可行解比 “贪心算法” 得到的利润还多。差分数组中除了差分为正数的项以外,还有就是差分为 0 的项与差分为负数的项。“贪心算法” 是所有差分为正数的项的和。
1、如果可行解在 “贪心算法” 的基础上,选择了差分为 0 的项,得到的结果与“贪心算法”得到的结果一样,因此加上差分为 0 的项不会比“贪心算法”得到的结果更好;
2、如果可行解在 “贪心算法” 的基础上,选择了差分为负数的项,加上一个负数得到的结果一定比 “贪心算法” 得到的结果要少,加上差分为负数的项,一定比 “贪心算法” 得到的结果更少;
3、如果可行解在 “贪心算法” 的基础上,去掉了任何一个差分为正数的项,同上,得到的结果一定比 “贪心算法” 得到的结果要小,因此,“贪心算法” 的所有组成项不能删去任何一个。
综上,除了 “贪心算法” 以外,找不到一个更优的解法,因此 “贪心算法” 就是最优解。(证完)