买卖股票的最佳时机
为什么股票问题适合用状态机描述?
- 问题: 最多完成两次交易, 求最大利润 (123);
- 原因: 每天的结果不只取决于价格, 还取决于"当前持有几次交易的什么状态";
- 结论: 把"是否持股、已完成几次交易"作为状态的维度, 用状态机在每天之间迁移;
状态如何定义?
- 状态维度:
dp[i][j]表示区间[0, i]内、处于状态j时的最大收益; - 五种状态:
0: 不进行任何操作;1: 第一次持有股票;2: 第一次不持有股票 (已完成第一次交易);3: 第二次持有股票;4: 第二次不持有股票 (已完成两次交易);
- 初值:
dp[0][0] = dp[0][2] = dp[0][4] = 0(空仓无收益),dp[0][1] = dp[0][3] = -prices[0](当天买入); - 答案:
max(dp[n-1]), 即dp[n-1][4];
状态之间如何迁移?
| 目标状态 | 不操作 | 操作 |
|---|---|---|
| 0 | dp[i-1][0] | 无 |
| 1 | dp[i-1][1] | 买入: dp[i-1][0] - prices[i] |
| 2 | dp[i-1][2] | 卖出: dp[i-1][1] + prices[i] |
| 3 | dp[i-1][3] | 买入: dp[i-1][2] - prices[i] |
| 4 | dp[i-1][4] | 卖出: dp[i-1][3] + prices[i] |
- 统一形式: 每个状态取"保持不变"和"由前一状态转移"的较大值;
const maxProfit = (prices) => {
const n = prices.length;
const dp = Array.from({ length: n }, () => new Array(5).fill(0));
dp[0][1] = dp[0][3] = -prices[0];
for (let i = 1; i < n; i++) {
dp[i][0] = dp[i - 1][0];
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
}
return Math.max(...dp[n - 1]);
};
- 复杂度: 时间
O(n), 空间O(n * k),k为状态数;
交易次数变化时如何扩展?
- 一次交易: 保留状态
0/1/2; - 两次交易: 扩展到
0/1/2/3/4; k次交易: 状态数变为2k + 1, 每天对每个状态做一次"不动或迁移"的比较;- 也可压缩: 用买入/卖出两个滚动变量按交易轮次迭代, 空间降到
O(k);