LeetCode188. 买卖股票的最佳时机 IV

状态机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<int>& 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<vector<vector<int>>> dp(n, vector<vector<int>>(K + 1, vector<int>(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次交易,不持有股票的状态就是最大能够得到的利润
    }
};
posted @ 2020-08-08 11:16  machine_gun_lin  阅读(224)  评论(0编辑  收藏  举报