LeetCode 188:买卖股票的最佳时机 IV
LeetCode 188:买卖股票的最佳时机 IV
问题本质与核心挑战
给定股票价格数组 prices
和最多交易次数 k
,求能获得的最大利润。需注意,不能同时参与多笔交易(必须卖出前一笔的股票)。
核心难点
- 状态复杂:需跟踪交易次数、是否持有股票两个维度的状态。
- 边界处理:初始状态(如第 0 天的买卖状态)和极端情况(如
k
过大远超实际可交易次数)需特殊处理。 - 状态转移:需推导“持有股票”和“不持有股票”两种状态的转移逻辑。
核心思路:动态规划
状态定义
buy[i][j]
:前i
天完成j
笔交易,当前持有股票的最大利润。sell[i][j]
:前i
天完成j
笔交易,当前不持有股票的最大利润。
状态转移
1. 持有股票(buy[i][j]
)
有两种可能:
- 第
i
天买入:前i-1
天不持有股票(状态sell[i-1][j]
),减去prices[i]
的买入成本。 - 之前已持有:前
i-1
天已持有股票(状态buy[i-1][j]
)。
公式:
buy[i][j]=max(buy[i−1][j], sell[i−1][j]−prices[i]) buy[i][j] = \max(buy[i-1][j],\ sell[i-1][j] - prices[i]) buy[i][j]=max(buy[i−1][j], sell[i−1][j]−prices[i])
2. 不持有股票(sell[i][j]
)
有两种可能:
- 第
i
天卖出:前i-1
天持有股票(状态buy[i-1][j-1]
),加上prices[i]
的卖出收益。 - 之前已卖出:前
i-1
天不持有股票(状态sell[i-1][j]
)。
公式:
sell[i][j]=max(sell[i−1][j], buy[i−1][j−1]+prices[i]) sell[i][j] = \max(sell[i-1][j],\ buy[i-1][j-1] + prices[i]) sell[i][j]=max(sell[i−1][j], buy[i−1][j−1]+prices[i])
边界条件
- 第 0 天持有股票:
buy[0][0] = -prices[0]
(只能买入第 0 天的股票),buy[0][j>0] = -infinity
(无法完成交易却持有股票,非法状态)。 - 第 0 天不持有股票:
sell[0][0] = 0
(无交易),sell[0][j>0] = -infinity
(无法完成交易却不持有股票,非法状态)。
特殊优化
若 k
大于实际可交易次数(n/2
,因为每次交易至少需要 2 天),直接取 k = n/2
即可(超过部分无意义)。
算法步骤详解(以示例 k=2, prices=[2,4,1]
为例)
步骤 1:初始化
n = 3
(天数),k = 2
(交易次数)。
优化 k
:n/2 = 1
,但示例中 k=2
实际有效(因交易可在 3 天内完成 2 次?不,实际 3 天最多 1 次完整交易。这里示例可能特殊,按原逻辑处理)。
初始化 buy
和 sell
数组:
buy[0][0] = -2
(第 0 天买入,成本 2),buy[0][1] = buy[0][2] = -infinity
。sell[0][0] = 0
(第 0 天无交易),sell[0][1] = sell[0][2] = -infinity
。
步骤 2:状态转移(遍历天数 i
和交易次数 j
)
天数 i=1
(价格 4)
-
j=0
:
buy[1][0] = max(buy[0][0], sell[0][0]-4) = max(-2, 0-4) = -2
sell[1][0] = max(sell[0][0], buy[0][-1]+4)
(j=0
时j-1
非法,取sell[0][0]
)→0
-
j=1
:
buy[1][1] = max(buy[0][1], sell[0][1]-4) = max(-inf, -inf-4) = -inf
(非法,因sell[0][1]
是-inf
)
实际应为:buy[1][1] = max(buy[0][1], sell[0][1]-4) → -inf
(但示例中后续会覆盖,这里简化)
sell[1][1] = max(sell[0][1], buy[0][0]+4) = max(-inf, -2+4) = 2
天数 i=2
(价格 1)
j=1
:
buy[2][1] = max(buy[1][1], sell[1][1]-1) = max(-inf, 2-1) = 1
sell[2][1] = max(sell[1][1], buy[1][0]+1) = max(2, -2+1) = 2
步骤 3:结果计算
最终最大利润为 sell[n-1][0...k]
中的最大值。示例中 sell[2][1] = 2
,与预期结果一致。
完整代码(Java)
class Solution {public int maxProfit(int k, int[] prices) {int n = prices.length;if (n == 0) return 0;// 优化:k 超过实际可能的交易次数,取 n/2 即可k = Math.min(k, n / 2);// 初始化 buy 和 sell 数组int[][] buy = new int[n][k + 1];int[][] sell = new int[n][k + 1];final int INF = Integer.MIN_VALUE / 2; // 避免溢出// 第 0 天的边界条件for (int j = 1; j <= k; j++) {buy[0][j] = INF;sell[0][j] = INF;}buy[0][0] = -prices[0];sell[0][0] = 0;// 遍历天数和交易次数for (int i = 1; i < n; i++) {for (int j = 0; j <= k; j++) {// 处理 buy[i][j]if (j <= k) {buy[i][j] = Math.max(buy[i-1][j], (sell[i-1][j] == INF ? INF : sell[i-1][j] - prices[i]));}// 处理 sell[i][j]if (j == 0) {sell[i][j] = sell[i-1][j]; // j=0 时 j-1 非法,无法交易} else {sell[i][j] = Math.max(sell[i-1][j], (buy[i-1][j-1] == INF ? INF : buy[i-1][j-1] + prices[i]));}}}// 取所有可能交易次数的最大值int maxProfit = 0;for (int j = 0; j <= k; j++) {maxProfit = Math.max(maxProfit, sell[n-1][j]);}return maxProfit;}
}
关键逻辑解析
- 数组初始化:用
INF
表示非法状态(避免溢出用Integer.MIN_VALUE / 2
)。 - 状态转移:判断
INF
避免非法状态参与计算。 - 结果计算:遍历所有可能的交易次数
j
,取sell
数组的最大值(因最终不持有股票利润更大)。
复杂度分析
- 时间复杂度:
O(n*k)
(遍历天数n
和交易次数k
)。 - 空间复杂度:
O(n*k)
(存储buy
和sell
数组,可优化为O(k)
滚动数组)。
通过动态规划清晰跟踪交易状态,结合边界条件和优化,可高效解决最多 k
次交易的股票问题。