动态规划 买卖股票的最佳时机含冷冻期

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

问题描述

给定一个数组 prices,其中 prices[i] 表示股票第 i 天的价格。设计算法计算最大利润,交易规则如下:

  • 可进行多次交易
  • 卖出股票后无法在第二天买入(冷冻期1天)
  • 买入前必须卖出之前的股票

示例

输入: [1,2,3,0,2]
输出: 3
解释: 
    第1天买入(1),第2天卖出(2),利润=1 → 进入冷冻期
    第4天买入(0),第5天卖出(2),利润=2
    总利润=1+2=3

算法思路

动态规划(DP 数组)

  1. 状态定义
    dp[i][0]: 第i天结束时,持有股票的最大利润
    dp[i][1]: 第i天结束时,不持有股票且处于冷冻期(即当天卖出了股票)的最大利润
    dp[i][2]: 第i天结束时,不持有股票且不处于冷冻期(即当天没有操作,且没有冷冻期限制)的最大利润
    注意:冷冻期表示卖出的后一天不能买入。因此,如果第i天卖出了股票,那么第i+1天不能买入。
  2. 状态转移
    • dp[i][0](第i天持有股票)
      • 可能在第i-1天就持有股票,即dp[i-1][0]
      • 或者在第i天买入股票。买入股票的条件是:第i-1天不能是冷冻期(即第i-1天没有卖出),所以只能从第i-1天的状态2(不持有且不处于冷冻期)转移过来,即dp[i-1][2] - prices[i]
        因此:dp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i])
    • dp[i][1](第i天卖出股票,所以第i天结束处于冷冻期)
      • 只能在第i天卖出股票,这意味着第i-1天必须持有股票,然后加上今天的价格。
        因此:dp[i][1] = dp[i-1][0] + prices[i]
    • dp[i][2](第i天不持有股票,且不处于冷冻期)
      • 说明第i天没有操作,且第i-1天也没有卖出(因为如果有卖出,第i天就是冷冻期,但这里是不处于冷冻期)。
      • 所以第i-1天可能是状态1(冷冻期)或者状态2(非冷冻期)。但是注意,如果第i-1天是冷冻期,那么第i天就会解冻,变成非冷冻期;如果第i-1天是非冷冻期,那么第i天继续非冷冻期。
        因此:dp[i][2] = max(dp[i-1][1], dp[i-1][2])
  3. 初始化
    第0天(i=0):
    dp[0][0] = -prices[0] // 买入股票
    dp[0][1] = 0 // 不可能在第0天卖出,可以认为卖出0(如果买入又卖出,则利润0)
    dp[0][2] = 0 // 第0天没有操作,不持有股票,且不是冷冻期
  4. 最终结果
    最后一天,不可能持有股票,所以取最后一天的不持有股票状态的最大值,即max(dp[n-1][1], dp[n-1][2])

动态规划(状态分离)

  1. 状态定义
    • hold:持有股票的最大利润
    • sold:当天卖出股票(进入冷冻期)的最大利润
    • cooldown:不持有股票且非冷冻期的最大利润
  2. 状态转移
    • 持有股票
      • 保持前日持有状态:hold
      • 今日买入(需从非冷冻期转移):cooldown - prices[i]
    • 卖出股票
      • 卖出前日持有的股票:hold + prices[i]
    • 冷冻期/非活跃
      • 取前日冷冻期和活跃状态的最大值:max(sold, cooldown)
  3. 初始化
    • 第0天持有:-prices[0]
    • 第0天卖出:0
    • 第0天冷冻期:0
  4. 结果max(sold, cooldown)(最终不持有股票)

代码实现

方法一:动态规划(状态变量)

class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length <= 1) return 0;
        
        // 初始化状态变量
        int hold = -prices[0];      // 持有股票
        int sold = 0;               // 当天卖出(进入冷冻期)
        int cooldown = 0;           // 不持有股票且非冷冻期(可操作状态)
        
        for (int i = 1; i < prices.length; i++) {
            // 保存前一日状态(避免覆盖)
            int prevHold = hold;
            int prevSold = sold;
            
            // 状态转移:
            // 持有股票:max(保持持有, 今日买入) 
            // 买入必须从前日非冷冻期转移(cooldown)
            hold = Math.max(prevHold, cooldown - prices[i]);
            
            // 卖出股票:只能卖出前日持有的股票
            sold = prevHold + prices[i];
            
            // 冷冻期/非活跃:max(前日冷冻期, 前日活跃状态)
            cooldown = Math.max(prevSold, cooldown);
        }
        
        // 最终结果取卖出状态和冷冻期状态的最大值
        return Math.max(sold, cooldown);
    }
}

方法二:动态规划(DP 数组)

class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length <= 1) return 0;
        
        int n = prices.length;
        // dp[i][0]: 持有股票
        // dp[i][1]: 当天卖出(进入冷冻期)
        // dp[i][2]: 不持有股票且非冷冻期
        int[][] dp = new int[n][3];
        
        // 初始化第0天
        dp[0][0] = -prices[0]; // 持有股票
        dp[0][1] = 0;          // 卖出状态(无操作)
        dp[0][2] = 0;          // 冷冻期/非活跃状态
        
        for (int i = 1; i < n; i++) {
            // 持有股票:max(保持持有, 今日买入)
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2] - prices[i]);
            
            // 卖出股票:卖出前日持有的股票
            dp[i][1] = dp[i-1][0] + prices[i];
            
            // 冷冻期/非活跃:max(前日冷冻期, 前日非活跃)
            dp[i][2] = Math.max(dp[i-1][1], dp[i-1][2]);
        }
        
        return Math.max(dp[n-1][1], dp[n-1][2]);
    }
}

算法分析

  • 时间复杂度:O(n),遍历一次价格数组
  • 空间复杂度
    • 状态变量:O(1)
    • DP数组:O(n)(实际为3n)

算法过程

输入prices = [1,2,3,0,2]
状态变量计算过程

初始化:hold=-1, sold=0, cooldown=0
Day1(2):
  hold = max(-1, 0-2) = -1   // 保持持有
  sold = -1 + 2 = 1          // 卖出获利1
  cooldown = max(0,0)=0      // 非活跃
Day2(3):
  hold = max(-1, 0-3) = -1   // 保持持有(冷冻期无法买入)
  sold = -1 + 3 = 2          // 卖出获利2
  cooldown = max(1,0)=1      // 解冻昨日卖出
Day3(0):
  hold = max(-1, 1-0)=1      // 买入(使用解冻后资金)
  sold = -1 + 0 = -1         // 无意义(实际取cooldown转移)
  cooldown = max(2,1)=2      // 保持非活跃
Day4(2):
  hold = max(1, 2-2)=1       // 保持持有
  sold = 1 + 2 = 3           // 卖出获利2(总利润3)
  cooldown = max(-1,2)=2     // 非活跃
结果:max(3,2)=3

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1: 标准示例
    int[] prices1 = {1,2,3,0,2};
    System.out.println("Test 1: " + solution.maxProfit(prices1)); // 3
    
    // 测试用例2: 价格持续上升
    int[] prices2 = {1,2,3,4,5};
    System.out.println("Test 2: " + solution.maxProfit(prices2)); // 4
    
    // 测试用例3: 价格持续下降
    int[] prices3 = {5,4,3,2,1};
    System.out.println("Test 3: " + solution.maxProfit(prices3)); // 0
    
    // 测试用例4: 波动价格
    int[] prices4 = {2,1,4};
    System.out.println("Test 4: " + solution.maxProfit(prices4)); // 3
    
    // 测试用例5: 空数组
    int[] prices5 = {};
    System.out.println("Test 5: " + solution.maxProfit(prices5)); // 0
    
    // 测试用例6: 两天可交易
    int[] prices6 = {1,2};
    System.out.println("Test 6: " + solution.maxProfit(prices6)); // 1
}

关键点

  1. 状态分离
    • 区分持有、卖出(冷冻期)、非活跃(可操作)三种状态
    • 冷冻期后自动转为非活跃状态
  2. 状态转移核心
    • 买入操作必须从非活跃状态转移
    • 卖出后强制进入冷冻期
  3. 初始化规则
    • 第0天持有股票的成本为 -prices[0]
    • 第0天无卖出操作,冷冻期状态为0
  4. 空间优化
    • 用三个变量代替DP数组
    • 按顺序更新避免状态覆盖

常见问题

  1. 为什么买入要从 cooldown 转移?
    冷冻期结束后(非活跃状态)才能买入,确保遵守卖出后隔日才能买入的规则。
  2. 如何理解 cooldown 状态?
    表示不持有股票且可立即操作的状态(非冷冻期)。
  3. 为何最后返回 max(sold, cooldown)
    最终必须卖出股票,且可能处于冷冻期或非活跃状态。
  4. 如何处理连续下跌行情?
    持有状态利润为负,卖出状态不会更新,最终返回0。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值