上一篇博客讲解了动态规划的定义,这一篇博客主要通过Leetcode上的实例来讲解一下我对动态规划的理解。
- 剑指 Offer 63. 股票的最大利润
- 122. 买卖股票的最佳时机 II
- 123. 买卖股票的最佳时机 III
- 188. 买卖股票的最佳时机 IV
- 309. 最佳买卖股票时机含冷冻期
- 714. 买卖股票的最佳时机含手续费
1. 股票的最大利润
对于千篇一律的解题思路我就不再赘述了(直接上图)!
通过这个实例请你先体会并总结一下动态规划的核心思想! 是不是觉得自己又行了,继续看下一题!
2. 买卖股票的最佳时机 II
对于千篇一律的解题思路我就不再赘述了(直接上图)!
// 0:持有现金
// 1:持有股票
// 状态转移:0 → 1 → 0 → 1 → 0 → 1 → 0
// 这两行调换顺序也是可以的
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
// 说明:
// 因为不限制交易次数,除了最后一天,每一天的状态可能不变化,也可能转移;
// 写代码的时候,可以不用对最后一天单独处理,输出最后一天,状态为 0 的时候的值即可。
接下来是这篇博客的核心,主要讲解一下通过这个实例我对动态规划的理解!
- 什么情况下使用动态规划?问题中前 i 天能够获得的最大利润不仅仅是由当天决定,而是与前 i-1 天有关。注意这是应用动态规划的决定性标志。而 前 i-1 天又由第 i -1 天与前 i-2 天有关。拆开来理解,前 i 天由 第 1,2,…,i-1,i 天所有情况决定,前 i -1 天由 第 1,2,…,i-2,i-1 天所有情况决定。依次类推,前 3 天由第 1,2,3 天共同决定。
这就是前面所说的动态规划应用于子问题重叠的情况,动态规划就可以很好地解决这种状态重复的问题。 - 如何刻画最优解的结构特征?即寻找状态转移方程。问题中前 i 天能够获得的最大利润不仅仅是由当天决定,而是与前 i-1 天有关。 如果你知道该问题需要使用动态规划解决,那么你也必定知道这句话的大意。但是究竟如何去寻找状态转移方程呢?注意:动态规划 dp[i] 研究的是前 i 天能够获得的最大利润,是前 i 天的最优解,是一系列子问题的最优解组成,因此必然是以递归的形式刻画这种结构特征。最简单形式的递归为 x = fun(x)。
回到本题
前 i 天能够获得的最大利润与前 i-1 天的 现存余额 与 持有股票 有关(因为允许多次买卖,可能已经买卖过一次了,因此存在余额与持有股票说法)。所以本题难点在于与过去两个状态有关。
假设cash为现存余额,hold为持有股票,目的是希望现存金额最大,所以在考虑前 i 天的现存余额时,必然是考虑今天的股票是该买还是该卖,如何衡量该买还是该卖?拿前 i-1 天的现存余额与卖掉这只股票相比取其较大值。( 有同学会问,为什么不拿卖掉股票与不卖掉股票相比,而是拿其与现存金额相比? 其实选择现存金额就是表示不卖掉该只股票,而选择卖掉股票就表示买了前面某一天的股票,当然今天这一只是否买入是由后面某天股票价格决定。说实话,靠文字叙述理解起来确实有点难>_< )。于是有:
cash[i] = max(cash[i - 1], hold[i - 1] + prices[i]); // 这里取最大值是希望现存金额较大,即在高价时卖出
hold[i] = max(hold[i - 1], cash[i - 1] - prices[i]); // 这里取最大值是希望花出去的成本小,即在低价时买入
-
如何定义最优解的值?最优解即为最终的状态值。在动态规划问题中,一般定义 dp[i] 为前面一些列状态的最优解。
-
如何计算最优解的值?一般采用自底向上的方式计算,即先计算最简单的子问题,如本题中第二天的股票是否买卖。(这个点细究起来有很多可玩的东西,今天没时间写了,有时间再更新!)
cash[1] = max(cash[0], hold[0] + prices[1]) = max(0, -7+1)
hold[1] = max(hold[0], cash[0] - price[1]) = max(-7, 0-1)
有同学问为什么这样就能得到全局最优解?
这就是贪心与动态规划的联系,本节不做详细讲解。
“贪心算法” 在每一步总是做出在当前看来最好的选择(局部最优解),我们只是寄希望这样的选择能导致全局最优解。
- “贪心算法” 和 “动态规划”、“回溯搜索” 算法一样,完成一件事情,是分步决策的;
- “贪心算法” 在每一步总是做出在当前看来最好的选择, “最好” 的意思往往根据题目而来,可能是 “最小”,也可能是 “最大”;
- 贪心算法和动态规划相比,它既不看前面(也就是说它不需要从前面的状态转移过来),也不看后面(无后效性,后面的选择不会对前面的选择有影响),因此贪心算法时间复杂度一般是线性的,空间复杂度是常数级别的。
这道题 “贪心” 的地方在于,对于 “今天的股价 - 昨天的股价”,得到的结果有 3 种可能:(1)正数(2)0(3)负数。贪心算法的决策是:只加正数。