LeetCode 309. 买卖股票的最佳时机含冷冻期(中等)

给定一个整数数组prices,其中第  prices[i] 表示第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

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

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

示例 1:

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

示例 2:

输入: prices = [1]
输出: 0

提示:

  • 1 <= prices.length <= 5000
  • 0 <= prices[i] <= 1000

问题分析

这个问题是"买卖股票的最佳时机"系列的变种,新增了一个重要的约束:卖出股票后的第二天不能买入(冷冻期)。

相比于前面的股票问题,我们需要考虑以下几点:

  1. 可以多次交易,但同一时间只能持有一股
  2. 卖出股票后的第二天不能买入
  3. 要求最大利润

这个约束会影响我们的决策,使得问题更加复杂。


解题思路

我们可以使用动态规划来解决这个问题。关键是要定义好状态和状态转移方程。

定义状态

考虑到冷冻期的存在,我们可以将每天结束时的状态分为三种:

  1. 持有股票状态 (hold):当天结束时持有一支股票
  2. 冷冻期状态 (cooldown):当天卖出了股票,下一天处于冷冻期
  3. 自由状态 (free):当天结束时没有持有股票,且不处于冷冻期

定义三个数组来记录这三种状态下的最大利润:

  • hold[i]:第i天结束时,持有股票状态下的最大利润
  • cooldown[i]:第i天结束时,处于冷冻期状态下的最大利润
  • free[i]:第i天结束时,自由状态下的最大利润

状态转移

每天的状态会根据前一天的状态更新,规则如下:

  1. 持有股票 (hold[i]):
    • 前一天就持有股票,今天什么都不做:hold[i-1]。
    • 前一天不持有股票且不在冷冻期,今天买入:free[i-1] - prices[i]。
    • 取两者的最大值:hold[i] = max(hold[i-1], free[i-1] - prices[i])。
  2. 不持有股票且处于冷冻期 (cooldown[i]):
    • 前一天持有股票,今天卖出:hold[i-1] + prices[i]。
    • 只有这一种情况:cooldown[i] = hold[i-1] + prices[i]。
  3. 不持有股票且不处于冷冻期 (free[i]):
    • 前一天就不持有股票且不在冷冻期,今天不动:free[i-1]。
    • 前一天处于冷冻期,今天冷冻期结束:cooldown[i-1]。
    • 取两者的最大值:free[i] = max(free[i-1], cooldown[i-1])。

状态转移方程如下:

  • hold[i] = max(hold[i-1], free[i-1] - prices[i])
    • 今天继续持有昨天的股票,或者昨天是自由状态今天买入
  • cooldown[i] = hold[i-1] + prices[i]
    • 今天卖出股票,所以昨天必须是持有状态
  • free[i] = max(free[i-1], cooldown[i-1])
    • 今天继续是自由状态,或者从冷冻期转为自由状态

初始化

  • 第一天 (i = 0):
    • hold[0] = -prices[0]:买入股票,利润为负的股价。
    • cooldown[0] = 0:第一天不可能卖出。
    • free[0] = 0:不买不卖,利润为 0。

最终答案

最终的最大利润是 max(cooldown[n-1], free[n-1]),因为最后一天要么是卖出股票,要么是自由状态,都比持有股票的利润更高。


详细执行过程图解

以示例 [1,2,3,0,2] 为例,让我们详细跟踪算法的执行过程:

初始状态(第1天):

  • 价格 = 1
  • hold = -1(买入股票)
  • cooldown = 0(不可能卖出)
  • free = 0(不操作)

第2天:

  • 价格 = 2
  • hold = max(-1, 0-2) = -1(维持昨天买入的状态)
  • cooldown = -1+2 = 1(今天卖出)
  • free = max(0, 0) = 0(维持自由状态)

第3天:

  • 价格 = 3
  • hold = max(-1, 0-3) = -1(维持买入状态)
  • cooldown = -1+3 = 2(今天卖出)
  • free = max(0, 1) = 1(从冷冻期转为自由状态)

第4天:

  • 价格 = 0
  • hold = max(-1, 1-0) = 1(今天买入股票)
  • cooldown = -1+0 = -1(今天卖出,但不如继续持有)
  • free = max(1, 2) = 2(从冷冻期转为自由状态)

第5天:

  • 价格 = 2
  • hold = max(1, 2-2) = 1(维持买入状态)
  • cooldown = 1+2 = 3(今天卖出)
  • free = max(2, -1) = 2(维持自由状态)

最终结果 = max(cooldown, free) = max(3, 2) = 3

股票交易时间线图解

   价格
    ^
3   |      *
    |
2   |   *        *
    |
1   |*
    |
0   +---------*---> 时间
     1  2  3  4  5
 
交易策略:
第1天: 买入 (价格=1)
第2天: 卖出 (价格=2, 利润=1)
第3天: 冷冻期
第4天: 买入 (价格=0)
第5天: 卖出 (价格=2, 利润=2)
总利润: 1+2=3

代码实现

Java 实现

class Solution {
    public int maxProfit(int[] prices) {
        // 边界条件检查
        if (prices == null || prices.length <= 1) {
            return 0;
        }
        
        int n = prices.length;
        
        // 初始化三个状态数组
        int[] hold = new int[n];      // 持有股票
        int[] cooldown = new int[n];  // 冷冻期(当天卖出)
        int[] free = new int[n];      // 自由状态(不持有股票且不在冷冻期)
        
        // 设置初始状态
        hold[0] = -prices[0];  // 第一天买入
        cooldown[0] = 0;       // 第一天不可能卖出(因为之前没买入)
        free[0] = 0;           // 第一天不操作
        
        // 动态规划过程
        for (int i = 1; i < n; i++) {
            // 第i天是持有股票状态,可能是之前买的,也可能是今天买的
            hold[i] = Math.max(hold[i-1], free[i-1] - prices[i]);

            // 第i天是冷冻期状态,必须是今天卖出了股票
            cooldown[i] = hold[i-1] + prices[i];
            
            // 第i天是自由状态,可能是之前就是自由状态,或者从冷冻期转来
            free[i] = Math.max(free[i-1], cooldown[i-1]);
        }
        
        // 最大利润是最后一天不持有股票的情况,取冷冻期和自由状态的较大值
        return Math.max(cooldown[n-1], free[n-1]);
    }
}

C# 实现

public class Solution {
    public int MaxProfit(int[] prices) {
        // 边界条件检查
        if (prices == null || prices.Length <= 1) {
            return 0;
        }
        
        int n = prices.Length;
        
        // 初始化三个状态数组
        int[] hold = new int[n];      // 持有股票
        int[] cooldown = new int[n];  // 冷冻期(当天卖出)
        int[] free = new int[n];      // 自由状态(不持有股票且不在冷冻期)
        
        // 设置初始状态
        hold[0] = -prices[0];  // 第一天买入
        cooldown[0] = 0;       // 第一天不可能卖出
        free[0] = 0;           // 第一天不操作
        
        // 动态规划过程
        for (int i = 1; i < n; i++) {
            // 第i天是持有股票状态,可能是之前买的,也可能是今天买的
            hold[i] = Math.Max(hold[i-1], free[i-1] - prices[i]);
            
            // 第i天是冷冻期状态,必须是今天卖出了股票
            cooldown[i] = hold[i-1] + prices[i];
            
            // 第i天是自由状态,可能是之前就是自由状态,或者从冷冻期转来
            free[i] = Math.Max(free[i-1], cooldown[i-1]);
        }
        
        // 最大利润
        return Math.Max(cooldown[n-1], free[n-1]);
    }
}

空间优化

我们可以注意到,在每一步的计算中,我们只需要前一天的状态。因此,可以将空间复杂度从O(n)优化到O(1):

Java 空间优化版本

class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length <= 1) {
            return 0;
        }
        
        int hold = -prices[0];     // 持有股票状态
        int cooldown = 0;          // 冷冻期状态
        int free = 0;              // 自由状态
        
        for (int i = 1; i < prices.length; i++) {
            int prevHold = hold;
            int prevCooldown = cooldown;
            int prevFree = free;
            
            // 更新三个状态
            hold = Math.max(prevHold, prevFree - prices[i]);
            cooldown = prevHold + prices[i];
            free = Math.max(prevFree, prevCooldown);
        }
        
        return Math.max(cooldown, free);
    }
}

C# 空间优化版本

public class Solution {
    public int MaxProfit(int[] prices) {
        if (prices == null || prices.Length <= 1) {
            return 0;
        }
        
        int hold = -prices[0];     // 持有股票状态
        int cooldown = 0;          // 冷冻期状态
        int free = 0;              // 自由状态
        
        for (int i = 1; i < prices.Length; i++) {
            int prevHold = hold;
            int prevCooldown = cooldown;
            int prevFree = free;
            
            // 更新三个状态
            hold = Math.Max(prevHold, prevFree - prices[i]);
            cooldown = prevHold + prices[i];
            free = Math.Max(prevFree, prevCooldown);
        }
        
        return Math.Max(cooldown, free);
    }
}

复杂度分析

  • 时间复杂度:O(n),其中 n 是价格数组的长度。我们只需要遍历一次数组。
  • 空间复杂度
    • 原始版本:O(n),需要三个长度为n的数组来存储状态
    • 优化版本:O(1),只需要常数级别的额外空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值