1715 字
9 分钟
Day42-动态规划 part09

买卖股票 IV#

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

本题和之前的买卖股票类似,都是在给定的交易次数内求最大利润。

我们同样可以使用一个二维的dp数组来表示状态。

dp[i][j] 表示第 i 天对股票的持有状态为 j 时的最大利润。j 的状态可以表示为:

  • j = 0:表示不操作
  • j = 1:表示第一次持有股票
  • j = 2:表示第一次卖出股票
  • j = 3:表示第二次持有股票
  • j = 4:表示第二次卖出股票

可以发现除了 j = 0 以外,当 j 为奇数时表示持有股票,为偶数时表示不持有股票。

因为有 k 次交易,所以二维的dp数组的大小可以定义为 2 * k + 1

1. 确定dp数组以及下标的含义#

根据上面的分析,我们可以定义一个二维的dp数组 dp[i][j]

dp[i][j] 表示第 i 天对股票的持有状态为 j 时的最大利润。

2. 确定递推公式#

到达 dp[i][1] (第一次持有股票)的状态有两种情况:

  • i 天不买入股票,保持原有状态 dp[i-1][1]
  • i 天买入股票,说明在第 i - 1 天不持有股票,买入股票后的所持金额为 dp[i-1][0] - prices[i]

在两者中选择最大的:

dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);

同理到达 dp[i][2] (第一次不持有股票)的状态有两种情况:

  • i 天不卖出股票,保持原有状态 dp[i-1][2]
  • i 天卖出股票,说明在第 i - 1 天持有股票,卖出股票后的所持金额为 dp[i-1][1] + prices[i]

在两者中选择最大的:

dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);

同理类比以上的状态,我们可以得到其他状态的递推公式:

for (int j = 0; j < 2 * k - 1; j += 2) {
    dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
    dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}

3. 初始化dp数组#

我们可以发现 dp[i][j] 的都是由 dp[i-1][j]dp[i-1][j+1] 递推而来,因此我们可以先初始化 dp[0][j]

  • 第0天没有操作,所以 dp[0][0] = 0

  • 第0天第一次持有股票,即首日就买入股票,所以 dp[0][1] = -prices[0]

  • 第0天第一次不持有股票,即首日就买入卖出,所以 dp[0][2] = 0

  • 第0天第二次持有股票,所以 dp[0][3] = -prices[0]

  • 第0天第二次不持有股票,所以 dp[0][4] = 0

所以在这里我们可以当 j 为偶数时,dp[0][j] = 0,当 j 为奇数时,dp[0][j] = -prices[0]

for (int j = 1; j < 2 * k; j += 2) {
    dp[0][j] = -prices[0];
}

4. 遍历顺序#

我们可以发现 dp[i][j] 的都是由 dp[i-1][j]dp[i-1][j+1] 递推而来,因此遍历顺序是从前往后

5. 整体代码#

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if (prices.size() == 0) return 0;
        vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0)); // 二维dp数组,大小为2*k+1
        for (int j = 1; j < 2 * k; j += 2) { // 初始化dp数组
            dp[0][j] = -prices[0];
        }
        for (int i = 1; i < prices.size(); i++) {
            for (int j = 0; j < 2 * k - 1; j += 2) {
                dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
                dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
            }
        }
        return dp[prices.size() - 1][2 * k];
    }
};

最佳买卖股票时机含冷冻期#

309. 最佳买卖股票时机含冷冻期

1. 确定dp数组以及下标的含义#

我们可以定义一个二维的dp数组来表示状态。

dp[i][j] 第i天状态为j,所剩的最多现金为dp[i][j]。

2. 确定递推公式#

具体可以区分出如下四个状态:

  • 状态一:持有股票状态(今天买入股票,或者是之前就买入了股票然后没有操作,一直持有)
  • 不持有股票状态,这里就有两种卖出股票状态
    • 状态二:保持卖出股票的状态(两天前就卖出了股票,度过一天冷冻期。或者是前一天就是卖出股票状态,一直没操作)
    • 状态三:今天卖出股票
  • 状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天

四种状态

我们可以将 j 状态表示为:

  • j = 0:状态1
  • j = 1:状态2
  • j = 2:状态3
  • j = 3:状态4

冷冻期的前一天,只能是 「今天卖出股票」状态,如果是 「不持有股票状态」那么就很模糊,因为不一定是 卖出股票的操作。

  • 到达到买入股票状态(状态一)即:dp[i][0],有两个具体操作:

    • 前一天持有股票,保持不变,dp[i-1][0]
    • 前一天不持有股票,今天买入股票,那么又会有两种情况:
      • 前一天是冷冻期(状态四),那么今天买入股票的金额为 dp[i-1][3] - prices[i]
      • 前一天是卖出股票(状态二),那么今天买入股票的金额为 dp[i-1][1] - prices[i]

    所以 dp[i][0] 的状态转移方程为:

    dp[i][0] = max(dp[i - 1][0], dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]);
  • 到达到卖出股票状态(状态二)即:dp[i][1],有两个具体操作:

    • 前一天就是卖出股票的状态,保持不变,dp[i-1][1]
    • 前一天是冷冻期(状态四),那么今天卖出股票的金额为 dp[i-1][3]

    所以有:

    dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
  • 到达今天卖出股票状态(状态三)即:dp[i][2],就只有一个操作:

    • 前一天一定是持有股票状态(状态一),那么今天卖出股票的金额为 dp[i-1][0] + prices[i]

    所以有:

    dp[i][2] = dp[i - 1][0] + prices[i];
  • 到达冷冻期状态(状态四)即:dp[i][3],就只有一个操作:

    • 昨天一定是卖出股票状态(状态三),今天冷冻期的金额为 dp[i-1][2]

    所以有:

    dp[i][3] = dp[i - 1][2];

因此我们可以得到状态转移方程:

dp[i][0] = max(dp[i - 1][0], dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
dp[i][2] = dp[i - 1][0] + prices[i];
dp[i][3] = dp[i - 1][2];

3. 初始化dp数组#

对于第0天的状态,我们可以直接初始化:

  • 状态一:dp[0][0] = -prices[0],表示第0天买入股票。
  • 状态二:dp[0][1] = 0
  • 状态三:dp[0][2] = 0
  • 状态四:dp[0][3] = 0

4. 遍历顺序#

我们可以发现 dp[i][j] 的都是由 dp[i-1][j]dp[i-1][j+1] 递推而来,因此遍历顺序是从前往后

5. 整体代码#


class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if (n == 0) return 0;
        vector<vector<int>> dp(n, vector<int>(4, 0));
        dp[0][0] -= prices[0]; // 持股票
        for (int i = 1; i < n; i++) {
            dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]));
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
            dp[i][2] = dp[i - 1][0] + prices[i];
            dp[i][3] = dp[i - 1][2];
        }
        return max(dp[n - 1][3], max(dp[n - 1][1], dp[n - 1][2]));
    }
};