Leetcode309-Best Time to Buy and Sell Stock with Cooldown图文详解
网上关于本题的解读,或是状态机,或是动态规划,但是大多都只是把公式列出来,很抽象,很少回答为什么该解法能求出最终解,本人花了些功夫分析了本题的本质,结合网上各路博客的解读,画了些图以帮助大家理解。
一,状态转换
首先,对于任意i天开始(注意是第i天开始,第i天的操作还没发生)的时候,有三种可能的状态:
S0,手中没有股票,可以买;S1,手中有股票,可以卖;S2,手中没有股票,也不能买,即昨天刚卖,今天cooldown;
在这三种状态下,分别可以执行买卖等操作,即第i天的操作之后,到达第i+1天的状态,转换图如下:

由此,我们从第1天(下标为0)开始,画出全部的状态转换图,如下:

该图中,每一条从0到n的路径表示一种可能的交易方式,则该状态转换图包含了全部可能的交易方式。这就是解空间,有了它我们才能想办法在其中求一个最大值,接下来就是动态规划的内容了。
二,动态规划
上一段中的状态好理解,但是在程序中如何表示和实现呢?尤其是,如何由这些状态得出最终我们想要的最大收益值呢?
按照暴力的解法思考,上面已经表示出了所有的路径,到第i天的不同状态有多种路径,每种路径最终的导致的收益都不一样。现在我们只存储到第i天的不同状态的所有路径中的最大的收益值,那么就来到了很多解法都提到的3个状态数组,我们对应于3种状态先设下3个数组,每个数组第i项存储的是:到第i天开始时候,如果是该状态,此时最大收益(注意此时第i天的操作依然没发生,所以最后的最大值还要考虑该天的操作,先按下不表)。这个最大值的求法就是我们熟悉的动态规划了。如下图:

假设我们已经求出了第i天的3种状态下的最大收益值,即三个数组中的第i项。第i+1天的最大值,只由第i天的值决定,即是如下三个公式:
s0[i+1] = max(s0[i], s2[i]);
s1[i+1] = max(s0[i] - prices[i], s1[i]);
s2[i+1] = s1[i] + prices[i];
这里可以先缓一缓,思考一下这里第i+1项是否只由第i项决定,因为这是该题能用动态规划求解的基础。动态规划的定义是,原问题可以转化为一个或多个更小的问题来接。在本题中,第i+1天的最大值,无论它是从哪条路径过来的,都要经过第i天的3中状态之一。假如第i+1天的最大值,不对应于第i天的最大值之一,那么它就不是最大值,因为我们能从第i天的最大值中求出一个更大的i+1天的值。如此则动态规划成立。
接下来的事就好办了,按照公式递推,就能求得最终第i天开始时的最大值。其中开始的几天可以按照边界条件来处理,也可以虚构一些第1天之前的值,使转换图完美循环(我是不太喜欢这种做法),最后,数组存储的是最后一天开始时候的值,最后一天还可能有操作,如下图:

最后一天应该不会买,只会卖了,比较,取最大值就行了。
max(max(s0[len-1], s2[len-1]), s1[len-1] + prices[len-1]);//len就是数组长度,就是天数
附上AC代码:
1 class Solution { 2 public: 3 int maxProfit(vector<int>& prices) { 4 int len = prices.size(); 5 if(len == 0 || len == 1) 6 return 0; 7 if(len == 2) 8 return prices[1] > prices[0] ? prices[1] - prices[0] : 0; 9 10 vector<int> s0(len); 11 vector<int> s1(len); 12 vector<int> s2(len); 13 s0[2] = 0; 14 s1[2] = max(-prices[1], -prices[0]); 15 s2[2] = prices[1] - prices[0]; 16 for(int i = 3; i < len; i++) 17 { 18 s0[i] = max(s0[i-1], s2[i-1]); 19 s1[i] = max(s0[i-1] - prices[i-1], s1[i-1]); 20 s2[i] = s1[i-1] + prices[i - 1]; 21 } 22 return max(max(s0[len-1], s2[len-1]), s1[len-1] + prices[len-1]); 23 } 24 };
网上还有很多优化方法,比如转为两个状态的,以及取消数组的,我是懒得想了,把问题弄清楚最重要。
浙公网安备 33010602011771号