【动态规划】力扣309:最佳买卖股票时机含冷冻期

给定一个整数数组prices,其中 prices[i] 表示第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例:

输入: prices = [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

不要关注冷冻期!只关注卖出的那一天!
题目中定义的【冷冻期】 = 卖出的那一天的后一天,题目设置冷冻期的意思是,如果昨天卖出了,今天不可买入,那么关键在于哪一天卖出,只要在今天想买入的时候判断一下前一天是不是刚卖出 即可,所以关键的一天其实是卖出的那一天,而不是卖出的后一天。

因为当天卖出股票实际上也是属于【不持有】的状态,那么第i天如果不持有,那这个“不持有”就有了两种状态:

  1. 本来就不持有,不是因为当天卖出了才不持有的;
  2. 第i天因为卖出了股票才变得不持有

持有股票也有两种状态:

  1. 当天买入
  2. 非今天买入,只是尚未卖出

所以对于每一天i,都有可能是四种状态:
0.根本不持股,定义其最大收益dp[i][0];
1.不持股且是因为当天卖出了,定义其最大收益dp[i][1];
2.持股且是今天买入的,定义其最大收益dp[i][2];
3.持股且非今天买入,定义其最大收益dp[i][3]。

初始化:
dp[0][0] = 0;//本来就不持有,啥也没干
dp[0][1] = 0;//可以理解成第0天买入又卖出,那么第0天就是“不持股且当天卖出了”这个状态了,其收益为0,所以初始化为0是合理的
dp[0][2] = dp[0][3] = -1 * prices[0] //第0天只买入,收益为负

  1. 第i天不持股且没卖出的状态dp[i][0],也就是我没有股票,而且还不是因为我卖了它才没有的,那换句话说是从i-1天到第i天转移时,它压根就没给我股票!所以i-1天一定也是不持有,那就是不持有的两种可能:i-1天不持股且当天没有卖出dp[i-1][0];i-1天不持股但是当天卖出去了dp[i-1][1];
    所以: dp[i][0]=max(dp[i-1][0],dp[i-1][1])
  2. 第i天不持股且当天卖出了,这种就简单了,那就是说昨天我一定是持股的,要不然我今天拿什么卖啊。要么昨天买入的而持有,要么之前买入的而昨天持有,昨天持股的收益加上今天卖出得到的新收益,就是 max(p[i-1][2], p[i-1][3])+prices[i]啦
    所以:dp[i][1]=max(p[i-1][2], p[i-1][3])+prices[i]
  3. 第i天持股dp[i][1],今天我持股,是因为昨天我不持股且今天买入,但前提一定是昨天我一定没卖!因为如果昨天我卖了,那么今天我不能交易!也就是题目中所谓“冷冻期”的含义,只有昨天是“不持股且当天没卖出”这个状态,我今天才能买入!买入就要减掉成本!所以是 dp[i-1][0]-prices[i]
  4. 第i天持股dp[i][1],今天我持股,是因为昨天我就持股,今天继承昨天的,而昨天可能买入了,也可能是继承的,所以是 max(dp[i-1][2],dp[i-1][3])。

如果在最后一天(第 n-1 天)结束之后,手上仍然持有股票,显然是没有任何意义的。所以最后一天的最大收益有两种可能,而且一定是“不持有”状态下的两种可能,把这两种“不持有”比较一下大小,返回即可。

作者:jin-ai-yi
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/solution/fei-zhuang-tai-ji-de-dpjiang-jie-chao-ji-tong-su-y/

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        dp = [[0] * 4 for _ in range(n)]
        dp[0][0] = 0 # 根本不持有股票
        dp[0][1] = 0 # 不持有股票,今天卖出
        dp[0][2] = -1 * prices[0] # 持有股票,今天买入
        dp[0][3] = -1 * prices[0] # 持有股票,非今天买入
        for i in range(1, n):
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1])
            dp[i][1] = max(dp[i - 1][2], dp[i - 1][3]) + prices[i]
            dp[i][2] = dp[i - 1][0] - prices[i]
            dp[i][3] = max(dp[i - 1][2], dp[i - 1][3])
        return max(dp[n - 1][0], dp[n - 1][1])

在上面的状态转移方程中,每一天的状态 dp[i][0]、dp[i][1] 、dp[i][2]、dp[i][3] 只与前一天的状态有关,因此可以基于滚动数组的思想进行对状态空间进行滚动优化:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        if not prices:
            return 0
        f0, f1, f2, f3 = 0, float('-inf'), -prices[0], -prices[0] # 不合理的初始状态均设为一个较大的负值 
        for i in range(1, n):
            f0, f1, f2, f3 = max(f0, f1), max(f2, f3) + prices[i], f0 - prices[i], max(f2, f3)
        return max(f0, f1)

时间复杂度:O(n),其中 n 为数组prices 的长度。
空间复杂度:O(n) 或 O(1)。需要 3n 的空间存储动态规划中的所有状态,对应的空间复杂度为 O(n)。如果使用空间优化,空间复杂度可以优化至 O(1)。

posted @ 2022-04-28 18:38  Vonos  阅读(128)  评论(0)    收藏  举报