大家好!在上一篇博客中,我们搞定了股票系列的入门题 121. 买卖股票的最佳时机(一次交易限制)。今天我们趁热打铁,拆解它的进阶版——122. 买卖股票的最佳时机 II。这道题取消了“一次交易”的限制,允许多次买卖,核心思路依然是贪心,但策略需要稍作调整。下面我们还是从题目理解、思路分析、代码拆解到边界测试,一步步把这道题讲透。
一、题目回顾:核心差异要分清
先明确 122 题的要求,重点对比 121 题找差异:
-
给定数组
prices,prices[i]表示第i天的股票价格。 -
交易规则升级:可多次买卖,但任何时候最多持有 1 股(必须先卖出再买入,不能持仓多股);甚至可在同一天多次买卖(比如当天买当天卖,虽利润为 0 但不违规)。
-
目标:获取最大利润(无利润时仍返回 0)。
用两个示例直观理解:
-
示例 1:输入
[7,1,5,3,6,4],输出 7。解释:第 2 天买(1)→ 第 3 天卖(5)(利润 4);第 4 天买(3)→ 第 5 天卖(6)(利润 3);总利润 4+3=7。 -
示例 2:输入
[1,2,3,4,5],输出 4。解释:第 1 天买→第 2 天卖(1)、第 2 天买→第 3 天卖(1)、第 3 天买→第 4 天卖(1)、第 4 天买→第 5 天卖(1),总利润 4;或直接第 1 天买→第 5 天卖(利润 4),结果一致。 -
示例 3:输入
[7,6,4,3,1],输出 0。解释:股价持续下跌,任何买卖都亏,不交易最赚。
核心差异总结:121 题是“单次交易找最大差价”,122 题是“多次交易累加所有正向差价”——这是贪心策略调整的关键。
二、解题思路:贪心升级,赚尽每一分正向差价
先思考暴力解法:遍历所有可能的交易组合(多次买入卖出的排列),计算总利润取最大值。但暴力解法时间复杂度是 O(2ⁿ)(每两天都有“买/卖”选择),n 稍大就会超时,显然不可行。
回到贪心思路——121 题是“找全局最大差价”,122 题该怎么调整?其实核心更简单:只要后一天的股价比前一天高,就进行一次“当天买、次日卖”的交易,把这部分正向差价累加起来,总利润就是最大的。
为什么这个思路成立?我们可以把股价走势想象成“上坡和下坡”:
-
上坡段(后一天 > 前一天):每一段上坡的差价都是能赚到的利润,累加所有上坡差价就是最大利润。比如
[1,2,3,4],上坡差价是 1(2-1)+1(3-2)+1(4-3)=3,和直接 4-1=3 结果一样。 -
下坡段(后一天 ≤ 前一天):不交易,避免亏损。
再对应到题目给出的代码思路:代码中用 minP 记录“当前持仓的买入价”,如果当天股价 > minP,就把差价加入利润(相当于卖出),然后立即更新 minP 为当天股价(相当于当天又买入,为后续差价做准备);如果当天股价 ≤ minP,就更新 minP 为当天股价(换更低的买入价)。本质上,这和“累加所有正向差价”是等价的,只是实现方式更简洁。
三、代码实现与逐行解析
下面是题目给出的 TypeScript 代码,我们逐行拆解,同时对比 121 题的代码找不同:
function maxProfit(prices: number[]): number {
// 1. 获取数组长度,避免循环中重复计算(和 121 题一致)
const pL = prices.length;
// 2. 初始化最小买入价为无穷大(和 121 题一致)
let minP = Infinity;
// 3. 初始化最大利润为 0(和 121 题一致)
let res = 0;
// 4. 遍历每一天的股价
for (let i = 0; i < pL; i++) {
// 5. 核心差异:只要当前股价 > 最小买入价,就累加差价(相当于卖出)
if (prices[i] > minP) {
res += prices[i] - minP;
}
// 6. 核心差异:无论是否卖出,都更新最小买入价为当前股价(相当于重新买入/换更低买入价)
minP = prices[i];
}
// 7. 返回最大利润
return res;
};
关键代码解析(重点看和 121 题的差异)
-
初始化部分(
pL、minP、res):和 121 题完全一致,minP = Infinity确保第一天股价能正常更新买入价。 -
核心判断
if (prices[i] > minP) res += prices[i] - minP;:-
和 121 题的“取最大值更新 res”不同,这里是“累加差价”——因为允许多次交易,每一次正向差价都要赚到手。
-
比如输入
[1,2,3,4]:i=1 时 2>1,res +=1(res=1);i=2 时 3>2,res +=1(res=2);i=3 时 4>3,res +=1(res=3),最终得到总利润 3。
-
-
强制更新
minP = prices[i]:-
这是 121 题没有的关键步骤!无论当天是否卖出,都把买入价更新为当前股价。
-
如果当天卖出了(prices[i] > minP):更新 minP 相当于“当天卖出后立即以当前价买入”,为后续计算下一段差价做准备。
-
如果当天没卖出(prices[i] ≤ minP):更新 minP 相当于“换更低的买入价”,保证后续能赚到更多差价。
-
和 121 题代码核心差异总结
| 对比维度 | 121 题(单次交易) | 122 题(多次交易) |
|---|---|---|
| 利润更新方式 | res = Math.max(res, 差价)(取全局最大) | res += 差价(累加所有正向差价) |
| minP 更新时机 | 仅当当前股价 < minP 时更新(保留历史最低买入价) | 每次循环都更新(卖出后重新买入/换更低买入价) |
| 核心逻辑 | 找“一次买入-卖出”的最大差价 | 赚尽每一段“后一天>前一天”的差价 |
四、复杂度分析
和 121 题一样,这是最优复杂度:
-
时间复杂度:O(n)。仅遍历数组一次,n 为数组长度,无额外嵌套循环。
-
空间复杂度:O(1)。仅使用 3 个变量(pL、minP、res),额外空间与数组长度无关。
为什么这是最优?因为要统计所有正向差价,必须遍历一次数组获取所有股价的先后关系,无法做到比 O(n) 更优。
五、边界案例测试
用极端场景验证代码的鲁棒性:
-
边界情况 1:数组长度为 1。输入
[5],无法完成任何交易(买入后无卖出时机),循环中 minP 被更新为 5,但无差价累加,res 保持 0,正确。 -
边界情况 2:股价持续下跌。输入
[7,6,4,3,1],每次循环中 prices[i] ≤ minP,无差价累加,res 始终为 0,正确。 -
边界情况 3:股价持续上涨。输入
[1,2,3,4,5],每次循环都累加差价(1+1+1+1=4),最终返回 4,正确。 -
边界情况 4:股价波动剧烈(有涨有跌)。输入
[3,2,6,5,0,3],过程拆解: 最终返回 7,正确(最佳交易:2→6 赚 4,0→3 赚 3,总 7)。-
i=0(3):3 < 无穷大 → 不累加,minP=3;
-
i=1(2):2 < 3 → 不累加,minP=2;
-
i=2(6):6 > 2 → res +=4(res=4),minP=6;
-
i=3(5):5 < 6 → 不累加,minP=5;
-
i=4(0):0 < 5 → 不累加,minP=0;
-
i=5(3):3 > 0 → res +=3(res=7),minP=3;
-
-
边界情况 5:同一天买卖。输入
[2,2,2],每次循环中 prices[i] = minP,无差价累加,res=0,正确(多次买卖无利润,和不交易一致)。
六、总结与拓展
122 题的核心是“贪心策略的进阶应用”——从 121 题的“找全局最大”升级为“赚尽局部所有正向收益”。解题的关键是想通:多次交易的最大利润,本质就是累加所有“后一天股价 > 前一天股价”的差价,而题目给出的代码用“动态更新买入价”的方式,简洁高效地实现了这个逻辑。
拓展思考:
-
如果题目再加限制(比如最多交易 2 次、有冷冻期、有手续费),贪心还能用吗?大部分情况需要用动态规划(DP)来解决,因为限制条件会让“局部最优”无法推导“全局最优”。
-
122 题的解法还有一个形象的名字——“峰谷法”:把股价的每个低点(谷)作为买入点,每个高点(峰)作为卖出点,累加峰谷差价就是最大利润。题目代码的逻辑和峰谷法完全等价,只是实现更简洁。
最后建议:把 121 题和 122 题的代码放在一起对比,仔细体会“单次交易”和“多次交易”在贪心策略上的差异,这能帮你更好地理解贪心算法的“灵活适配性”。如果有疑问,欢迎在评论区交流~