状态机dp,参考labuladong的题解
首先考虑状态表示,这题有三种状态,天数、当前进行的最大交易次数,以及当前是否持有股票。所以我们可以用dp[i][k][0或1]表示:当前是第i天(i从0开始),当前允许的最大交易次数为k(这里我们认为交易次数是买入股票的次数),0表示当前不持有股票,1表示当前持有股票 的最大收益。
列出了状态表示之后,我们需要考虑状态转移方程,这里给出labuladong画的状态转移图:
其中buy、sell、rest分别表示买入、卖出、不操作 三种操作。
从状态转移图可以看出,对于一个不持有股票的状态,只能由sell和rest两种状态转移过来,也就是说可能(1)前一天持有股票,把股票卖掉了变成今天的状态;和(2)前一天就不持有股票,所以今天还是不持有股票。
对于一个持有股票的状态,只能由buy和rest两种状态转移过来,也就是说可能(1)前一天不持有股票,今天买了个股票;和(2)前一天就持有股票了,今天不操作。
所以我们知道各种状态是如何转移的,我们可以考虑状态转移方程。
每一天,对于dp[i][k][0](表示第i天,进行了k次交易,当前不持有股票),我们已经知道它是由dp[i - 1][k][0](前一天也是不持有股票)和dp[i - 1][k][1](前一天持有股票,今天卖掉了)两种状态转移而来,由于dp[i - 1][k][0]转移到dp[i][k][0]表示rest(无操作),所以收益不变:dp[i][k][0] = dp[i - 1][k][0];而从dp[i - 1][k][1]转移到dp[i][k][0]表示在第i天卖出股票,所以是dp[i][k][0] = dp[i - 1][k][1] + prices[i];(卖出股票获得了prices[i]的收益)
我们当然是希望收益越大越好,所以要对这两个状态取一个max: dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
对于dp[i][k][1](表示第i天,进行了k次交易,当前持有股票),我们知道它是由dp[i - 1][k][1](前一天也持有股票)和dp[i - 1][k - 1][0](前一天不持有股票,今天买了股票)两种状态转移而来,由于dp[i - 1][k][1]转移到dp[i][k][1]表示rest(无操作),所以收益不变: dp[i][k][1] = dp[i - 1][k][1];而从dp[i - 1][k - 1][0]转移到dp[i][k][1]表示在第i天买入股票,所以是dp[i][k][1] = dp[i - 1][k - 1][1] - prices[i];(需要花prices[i]的价格买入股票)
同样,我们也希望收益越大越好,所以要对这两个状态取较大值:dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
所以我们可以遍历每天天数和交易次数,两重循环就确定了所有状态,最后的答案就是dp[n - 1][K][0](这里n是prices数组的大小,表示所有天数,K是最大可以交易/买入的次数),这个状态表示在最后一天,最多交易K次,不持有股票的最大收益。为什么不是dp[n - 1][K][1]呢?因为1表示还持有股票,而买股票需要花钱,卖了才能赚钱,所以显然dp[n - 1][K][0]比dp[n - 1][K][1]大。
现在状态表示有了,状态转移方程也有了,只需要确定一下边界情况,我们就可以开始写代码了。
我们看一下我们的状态转移方程:
dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
每个状态都是从前一天转移过来的,所以对于第0天(i == 0),我们需要单独考虑。
枚举所有的交易次数k,我们都有dp[0][k][0] = 0, 表示第0天,不管进行多少次交易(虽然实际上一次都没进行,因为买入和卖出不能在同一天,所以如果第0天没有股票,就是一次交易都没有,但是由于数组需要,我们还是要枚举所有k),收益都是0(因为不持有股票)。这个状态也可以理解为在第0天买入k次又卖出k次(虽然实际上不能这么干,但是我们还是要枚举所有的k,方便后面的计算),所以等于不赚钱。
枚举所有的交易次数k,我们都有dp[0][k][1] = -prices[0],表示第0天,不管进行多少次交易(虽然最多进行一次,也就是在第0天买入股票),收益都是-prices[0](表示第0天花了prices[i]的钱买了支股票)。
dp[0][k][0], dp[0][k][1]这两个状态就是边界情况了,然后我们可以枚举天数和交易次数进行状态转移了。
这里要注意一下,如果最大可交易次数K >= n / 2,由于买入和卖出需要两天,如果可交易次数超过天数的一半,等价于没有交易次数K的限制,这种情况也要单独处理,实际上,这就是股票系列的第二题,计算方法是枚举每一天和后一天之间有没有套利空间(也就是后一天价格大于前一天价格),有的话就把答案加上prices[i + 1] - prices[i](表示得到的利润),表示前一天价格低于后一天的话,我昨天买个股票今天立马就卖了。
枚举完所有天数,每天能够得到的利润之和就是最终答案。
代码如下:
class Solution {
public:
int maxProfit(int K, vector& prices) {
int n = prices.size();
if(K >= n / 2) { //等价于交易次数无限,K的限制无效,单独处理
int res = 0;
for(int i = 0; i + 1 < n; ++i) {
if(prices[i + 1] > prices[i]) {
res += prices[i + 1] - prices[i];
}
}
return res;
}
vector>> dp(n, vector>(K + 1, vector(2)));
for(int i = 0; i < n; ++i) {
for(int k = 1; k <= K; ++k) {
if(i == 0) { //边界情况,i为0时i - 1越界,单独处理
dp[i][k][0] = 0;
dp[i][k][1] = -prices[i];
continue;
}
dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
}
}
return dp[n - 1][K][0]; //最后一天,进行K次交易,不持有股票的状态就是最大能够得到的利润
}
};