LeetCode 121 & 122:股票买卖问题(DP 对比题解)✅
📌 题目列表
| 题号 | 题目 | 限制 |
|---|---|---|
| 121 | 买卖股票的最佳时机I | 只能交易 1 次 |
| 122 | 买卖股票的最佳时机II | 可以交易 无限次 |
📖 内容概要
给定一个数组 prices,其中 prices[i] 是第 i 天的股票价格。
你需要选择 买入和卖出时机,使利润最大。
- 121:只能买卖一次
- 122:可以多次买卖(同一天不能同时买卖)
✅ 动态规划
✅ 状态机思想
✅ 面试高频题
💡 统一 DP 定义(两题通用)
java
dp[i][0] = 第 i 天结束时,不持有股票的最大利润
dp[i][1] = 第 i 天结束时,持有股票的最大利润
🔁 状态转移(核心)
不持有股票 dp[i][0]
java
dp[i][1] = max(
前一天就不持有,
前一天持有 + 今天卖出
)
✅ 两题 完全一致
持有股票 dp[i][1]
| 题目 | 转移方程 | 含义 |
|---|---|---|
| 121 | max(dp[i-1][1], -prices[i]) |
只能买一次 |
| 122 | max(dp[i-1][1], dp[i-1][0] - prices[i]) |
可以反复买 |
✅ 这是两道题的唯一区别
✅ 121 题:只能买卖一次(AC 代码)
java
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[len][2];
dp[0][0] = 0; // 不持股
dp[0][1] = -prices[0]; // 持股
for (int i = 1; i < len; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], -prices[i]); // 只能买一次
}
return dp[len - 1][0];
}
}
✅ 关键点
- 第二次买入时:
- 之前不能有利润
- 只能是
-prices[i]
✅ 122 题:可以无限交易(AC 代码)
java
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[len][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < len; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[len - 1][0];
}
}
✅ 关键点
- 再次买入时:
- 使用的是 之前不持股的最大利润
🔍 两题核心差异对比
| 对比项 | 121(一次) | 122(无限次) |
|---|---|---|
| 是否可多次买卖 | ❌ | ✅ |
| 持有状态转移 | -prices[i] |
dp[i-1][0] - prices[i] |
| DP 含义 | 第一次买入 | 任意次买入 |
| 难度 | 中等 | 简单 |
⏱️ 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(n)(可优化为 O(1)) |
🚀 空间优化(通用)
java
int hold = -prices[0];
int cash = 0;
for (int i = 1; i < prices.length; i++) {
int prevCash = cash;
cash = Math.max(cash, hold + prices[i]);
hold = Math.max(hold, prevCash - prices[i]);
}
return cash;
✅ 一句话总结
121 限制"只能买一次",122 允许"反复买卖",区别仅在于持有股票的转移方程。