一、状态机模型思想 对于股票问题,我们通常定义两种状态:
● 持有状态 (holding):手上持有股票。
● 空仓状态 (not holding):手上不持有股票。 在某些题型中,还需要考虑交易次数(例如最多进行 (k) 次交易)或者其他约束(如冷冻期、手续费)。 状态转移方程一般写法 假设 $dp[i][k][0]$表示第 i 天、最多完成 k 次交易,且不持有股票的最大收益; $dp[i][k][1]$表示第 i 天、最多完成 k 次交易,且持有股票的最大收益。
常用的状态转移方程为:
● 转移到不持有状态 $dp[i][k][0] = \max(dp[i-1][k][0],\ dp[i-1][k][1] + \text{prices}[i])$ 解释:要么前一天就不持有,今天不操作;要么前一天持有,今天卖出(注意卖出完成一次交易)。 ● 转移到持有状态 $dp[i][k][1] = \max(dp[i-1][k][1],\ dp[i-1][k-1][0] - \text{prices}[i])$ 解释:要么前一天就持有,今天不操作;要么前一天不持有,今天买入(交易次数加1)。 附加条件会在状态转移时加以调整。 二、各大题型及其状态转移方程

  1. 买卖股票 I 题意:只允许进行一次交易。 思路:只需求出一个最低买入价与最高卖出价之差。 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/ 状态与转移 这里可以直接用贪心方法,但也可以构造 DP:
    ● 定义 ( dp[i][0] ) 为第 (i) 天不持有股票时的最大收益。
    ● 定义 ( dp[i][1] ) 为第 (i) 天持有股票时的最大收益。 初始状态: ● $dp[0][0] = 0$ ● $dp[0][1] = -\text{prices}[0]$ 转移方程(只允许一次买卖,因此买入后不能再次买入): $dp[i][0] = \max(dp[i-1][0],\ dp[i-1][1] + \text{prices}[i])$ $dp[i][1] = \max(dp[i-1][1],\ -\text{prices}[i])$ 示例代码(Java): class Solution { public int maxProfit(int[] prices) { int n = prices.length; if(n == 0) return 0; int dp0 = 0, dp1 = -prices[0]; for (int i = 1; i < n; i++) { dp0 = Math.max(dp0, dp1 + prices[i]); dp1 = Math.max(dp1, -prices[i]); } return dp0; } } class Solution { public int maxProfit(int[] prices) { int n = prices.length; if (n == 0) return 0;

     int min = prices[0];
     int max = 0;
     for (int i = 1; i < n; i++) {
         max = Math.max(max, prices[i] - min);
         min = Math.min(min, prices[i]);
     }
    
     return max;
    

    } }

  2. 买卖股票 II 题意:可以进行无限次交易,但不能同时持有多支股票。 思路:每天都可以选择买入或卖出。 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/ 状态与转移 初始状态: ● $dp[0][0] = 0$ ● $dp[0][1] = -\text{prices}[0]$ 转移方程: $dp[i][0] = \max(dp[i-1][0],\ dp[i-1][1] + \text{prices}[i])$ $dp[i][1] = \max(dp[i-1][1],\ dp[i-1][0] - \text{prices}[i])$ 示例代码(Java): class Solution { public int maxProfit(int[] prices) { int n = prices.length; if (n == 0) return 0;

     int[][] dp = new int[n][2];
     dp[0][0] = 0;
     dp[0][1] = -prices[0];
    
     for (int i = 1; i < n; i++) {
         //当前未持有的最大利润 = max(前一天未持有,前一天持有+今天卖了)
         dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
         //当前持有的最大利润 = max(前一天也持有,前一天未持有+今天买入(买入扣钱))
         dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
     }
     //最后一天一定是未持有利润最大,因为持有卖不了纯亏钱
     return dp[n - 1][0];
    

    } }

  3. 买卖股票 III 题意:最多进行两次交易。 思路:加入交易次数的维度 ( k ),状态转移参考状态定义 ( dp[i][k][0/1] )。

  4. 买卖股票的最佳时机 III - 力扣(LeetCode) 状态与转移 ● $dp[i][k][0] = \max(dp[i-1][k][0],\ dp[i-1][k][1] + \text{prices}[i])$ ● $dp[i][k][1] = \max(dp[i-1][k][1],\ dp[i-1][k-1][0] - \text{prices}[i])$ 注意:交易次数 ( k ) 从 1 开始。 示例代码(Java): class Solution { public int maxProfit(int[] prices) { int n = prices.length; if (n <= 1) return 0;

    // dp[i][k][0]:第 i 天进行 k 次交易且未持有股票的最大收益 // dp[i][k][1]:第 i 天进行 k 次交易且持有股票的最大收益 int[][][] dp = new int[n][3][2];

    // 初始化第 0 天的状态 for (int k = 0; k <= 2; k++) { dp[0][k][0] = 0; dp[0][k][1] = -prices[0]; // 第 0 天买入股票 }

    // 遍历每一天 for (int i = 1; i < n; i++) { for (int k = 1; k <= 2; k++) { // 最多交易 2 次 // 未持有股票 dp[i][k][0] = Math.max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]); // 持有股票 dp[i][k][1] = Math.max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]); } }

    return dp[n - 1][2][0]; // 最后一天,最多交易 2 次,未持有股票的最大收益 } }

  5. 买卖股票 IV 题意:最多进行 ( k ) 次交易。 思路:与股票 III 类似,但交易次数为参数,可动态变化。 状态转移方程与股票 III 类似,只不过循环 ( k ) 的上限为题目给定的 ( k ) 值。 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/ 状态与转移 ● $dp[i][j][0] = \max(dp[i-1][j][0],\ dp[i-1][j][1] + \text{prices}[i])$ ● $dp[i][j][1] = \max(dp[i-1][j][1],\ dp[i-1][j-1][0] - \text{prices}[i])$ 示例代码(Java): class Solution { public int maxProfit(int k, int[] prices) { int n = prices.length; if (n <= 1) return 0;

     // dp[i][k][0]:第 i 天进行 k 次交易且未持有股票的最大收益
     // dp[i][k][1]:第 i 天进行 k 次交易且持有股票的最大收益
     int[][][] dp = new int[n][k + 1][2];
    
     // 初始化第 0 天的状态
     for (int i = 0; i <= k; i++) {
         dp[0][i][0] = 0;
         dp[0][i][1] = -prices[0]; // 第 0 天买入股票
     }
    
     // 遍历每一天
     for (int i = 1; i < n; i++) {
         for (int j = 1; j <= k; j++) { // 最多交易 k 次
             // 未持有股票
             dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
             // 持有股票
             dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
         }
     }
    
     return dp[n - 1][k][0]; // 最后一天,最多交易 k 次,未持有股票的最大收益
    

    } }

  6. 带冷冻期的股票 题意:买卖股票后第二天不能买入,即存在冷冻期。 思路:状态转移时需考虑冷冻期的约束。
    https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/ 状态与转移 假设: ● $dp[i][0]$表示第 (i) 天未持有股票的最大收益
    ● $dp[i][1]$表示第 (i) 天持有股票的最大收益 初始状态: ● $dp[0][0] = 0$ ● $dp[0][1] = -\text{prices}[0]$ 转移方程: ● 卖出股票时: $dp[i][0] = \max(dp[i-1][0],\ dp[i-1][1] + \text{prices}[i])$ ● 买入股票时需要跳过前一天(冷冻期): $dp[i][1] = \max(dp[i-1][1],\ dp[i-2][0] - \text{prices}[i])$ 注意:当 ( i-2 < 0 ) 时需特殊处理。 示例代码(Java): class Solution { public int maxProfit(int[] prices) { int n = prices.length; if (n <= 1) return 0;

     int[][] dp = new int[n][2];
     dp[0][0] = 0;
     dp[0][1] = -prices[0];
    
     for (int i = 1; i < n; i++) {
         dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
         if (i > 2)
             dp[i][1] = Math.max(dp[i - 1][1], dp[i - 2][0] - prices[i]);
         else
             //如果i==1,那么只能在第一天买入或者在第二天买入
             dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
     }
    
     return dp[n - 1][0];
    

    } }

  7. 带手续费的股票 题意:每笔交易需要扣除一定手续费。 思路:在卖出时扣除手续费即可。
    状态与转移 初始状态: ● $dp[0][0] = 0$ ● $dp[0][1] = -\text{prices}[0]$ 转移方程: $dp[i][0] = \max(dp[i-1][0],\ dp[i-1][1] + \text{prices}[i] - \text{fee})$ $dp[i][1] = \max(dp[i-1][1],\ dp[i-1][0] - \text{prices}[i])$ 示例代码(Java): class Solution { public int maxProfit(int[] prices, int fee) { int n = prices.length; if (n == 0) return 0;

     int[][] dp = new int[n][2];
     dp[0][0] = 0;
     dp[0][1] = -prices[0];
    
     for (int i = 1; i < n; i++) {
         dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
         dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
     }
    
     return dp[n - 1][0];
    

    } }

总结

  1. 核心思想 利用状态机/动态规划记录每天的“持有”与“空仓”状态,同时根据题目要求增加交易次数、冷冻期、手续费等约束。
  2. 状态转移公式
    ○ 卖出操作:收益 = 前一天持有状态 + 当前卖出价格(有时扣除手续费)
    ○ 买入操作:收益 = 前一天空仓状态 - 当前买入价格(有时受冷冻期约束)
  3. 代码优化 对于状态仅依赖前一天的情况,可以用滚动数组或变量优化空间复杂度。