zoukankan      html  css  js  c++  java
  • 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次交易,不持有股票的状态就是最大能够得到的利润
        }
    };
    
  • 相关阅读:
    MYsql增删改查
    粘包问题
    模拟ssh远程执行命令
    Socket抽象层
    基于TCP协议的socket套接字编程
    TCP协议的三次握手和四次挥手
    大话OSI七层协议
    网络架构及其演变过程
    互联网和互联网的组成
    Windows安装MySQL
  • 原文地址:https://www.cnblogs.com/linrj/p/13456892.html
Copyright © 2011-2022 走看看