买卖股票 II 图解题解
这道题到底在问什么
- 输入
- prices=[7,1,5,3,6,4]
- 输出
- 7 (第2天买入1、第3天卖出5 赚4;第4天买入3、第5天卖出6 赚3;合计 7)
最优解:为什么这么做
一句话答案:LeetCode 122 买卖股票的最佳时机 II:可无限次买卖求最大利润,用贪心把每段相邻上涨都收下——今天比昨天高就累加差价、下跌不参与,一遍扫完,时间 O(n)、空间 O(1)。
无限次买卖,最多能赚多少钱
给一个数组 prices,prices[i] 是第 i 天的股价。可以在任意天买入、任意天卖出,还能反复交易,唯一的规矩是买入前手上不能压着没卖掉的股票。问把这些买卖凑到最好,最多赚多少。题面例子 prices=[7,1,5,3,6,4],答案是 7。
盯着最低价买、最高价卖,为什么只赚 5
第一反应是找全程最低那天买进、最高那天卖出。这串数里最低是 1、它之后的最高是 6,一进一出赚 5。可正确答案是 7,多出 2。差就差在一笔买卖只框得住一次「低进高出」,而这串价格里明明有两段各自向上的行情,硬塞进一笔就丢了其中一段。
为什么把每段上涨都收下,正好是最多
既然不限次数,就别再想「整段一次买卖」,改成盯着相邻两天。看一段上涨 a<b<c,从 a 拿到 c 赚的是 (c-a);把它拆开,(c-a) 正好等于 (b-a) 加 (c-b)——中间那天 b 卖掉再立刻买回,收益一分不差。所以一整段上涨,等价于把段里每一对相邻正差都收下。
推到全程就清楚了:把所有相邻的正差价加起来,覆盖的正是每一段上涨,一段不漏;下跌那些天差价是负的,根本不碰,也就绕开了所有亏损。既没漏掉能赚的、又没沾上会亏的,这个和既是能拿到的上界、也真能拿到。落到每天的动作就是——只要明天比今天高就把这段差价收进口袋、不去纠结更远的价格。
相邻高一格就累加,一遍扫到底
代码就照这个来:利润 profit 从 0 起,下标 i 从第 1 天走到最后一天,只要 prices[i]>prices[i-1],就把 prices[i]-prices[i-1] 这段差价加进 profit,不高就跳过,走完返回 profit。全程只从头到尾扫一遍,时间 O(n);除了一个累加变量不占别的地方,空间 O(1)。不用记住在哪天买、哪天卖,只认相邻的涨跌。
[7,1,5,3,6,4] 逐天比一遍,7 一笔笔落进口袋
从第 1 天起逐天比。1 和昨天 7 比是跌的,跳过,profit 还是 0;5 比昨天 1 高,收下 4,profit=4;3 比昨天 5 低,跳过;6 比昨天 3 高,收下 3,profit=7;最后 4 比昨天 6 低,跳过。走到头 profit=7。
下跌那天最容易也跟着记上一笔——负数会把攒下的利润吃回去,这种天只能跳过。另一个卡点是怕「同一天又买又卖」犯规而不敢累加,可相邻正差从不要求真在同天来回:a<b<c 时把 (c-a) 拆成两笔,和一整笔的账完全对得上。
▶ 动画逐步走查(共 24 步)——想跟着动画一帧帧对照就展开
- 3记住这条「明天涨就今买明卖、把每段涨幅全收下」,下面每一天都在套它。
- 4看第 1 天 1 和昨天 7:跌了 6。下跌段没钱赚,待会儿空仓躲过。
- 5这天不持有、什么都不做,所以不会亏,累计利润保持 0。
- 6看第 2 天 5 和昨天 1:贵了 4。明天比今天涨,说明这段可以「今买明卖」收下差价。
- 7把这 4 收进口袋:绿色两格就是刚吃进的上涨段,累计利润变成 4。
- 8看第 3 天 3 和昨天 5:跌了 2。下跌段没钱赚,待会儿空仓躲过。
- 9这天不持有、什么都不做,所以不会亏,累计利润保持 4。
- 10看第 4 天 6 和昨天 3:贵了 3。明天比今天涨,说明这段可以「今买明卖」收下差价。
- 11把这 3 收进口袋:绿色两格就是刚吃进的上涨段,累计利润变成 7。
- 12看第 5 天 4 和昨天 6:跌了 2。下跌段没钱赚,待会儿空仓躲过。
- 13这天不持有、什么都不做,所以不会亏,累计利润保持 7。
- 14看第 6 天 8 和昨天 4:贵了 4。明天比今天涨,说明这段可以「今买明卖」收下差价。
- 15把这 4 收进口袋:绿色两格就是刚吃进的上涨段,累计利润变成 11。
- 16看第 7 天 2 和昨天 8:跌了 6。下跌段没钱赚,待会儿空仓躲过。
- 17这天不持有、什么都不做,所以不会亏,累计利润保持 11。
- 18看第 8 天 5 和昨天 2:贵了 3。明天比今天涨,说明这段可以「今买明卖」收下差价。
- 19把这 3 收进口袋:绿色两格就是刚吃进的上涨段,累计利润变成 14。
- 20看第 9 天 9 和昨天 5:贵了 4。明天比今天涨,说明这段可以「今买明卖」收下差价。
- 21把这 4 收进口袋:绿色两格就是刚吃进的上涨段,累计利润变成 18。
- 22看第 10 天 3 和昨天 9:跌了 6。下跌段没钱赚,待会儿空仓躲过。
- 23这天不持有、什么都不做,所以不会亏,累计利润保持 18。
- 24看第 11 天 7 和昨天 3:贵了 4。明天比今天涨,说明这段可以「今买明卖」收下差价。
- 25把这 4 收进口袋:绿色两格就是刚吃进的上涨段,累计利润变成 22。
- 26所有绿色格子就是被吃进的上涨段。每一段上涨都收进口袋、每一段下跌都躲过,加总 22 就是无限次买卖能拿到的最大利润。
⚠️ 容易写错的地方
✗ 错:想找“全局最低买、全局最高卖”
✓ 对:本题可多次交易,应吃掉每一段上涨
不限次数时,分段收割 ≥ 只做一笔,把所有正差价都收下才最大
✗ 错:把下跌的差价也加进去
✓ 对:只加 prices[i]>prices[i-1] 的正差
下跌段不持有即可避开,加负差会减少利润
✗ 错:担心“同一天买卖”不合法
✓ 对:相邻正差累加等价于合法的低买高卖,无需真在同一天来回
a<b<c 时 (c-a) = (b-a)+(c-b),拆成两笔与一笔等价
完整代码(Python / C++ / Java)
Python
def maxProfit(prices):
profit = 0
for i in range(1, len(prices)):
if prices[i] > prices[i - 1]:
profit += prices[i] - prices[i - 1] # 吃进这段上涨
return profitC++
int maxProfit(vector<int>& prices){
int profit = 0;
for(int i = 1; i < prices.size(); i++){
if(prices[i] > prices[i - 1])
profit += prices[i] - prices[i - 1]; // 累加正差
}
return profit;
}Java
public int maxProfit(int[] prices) {
int profit = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] > prices[i - 1]) {
profit += prices[i] - prices[i - 1]; // 吃进这段上涨
}
}
return profit;
}复杂度
时间
O(n)
从第 1 天到最后一天扫一遍
空间
O(1)
只用一个利润累加变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 买卖股票 II 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么累加相邻正差价就等于最大利润?+
任一上升段 a<b<c,(c-a) 能拆成 (b-a) 加 (c-b),所以「整段一次买卖」和「逐日相邻买卖」赚的一样多。把所有相邻正差加起来,恰好覆盖每一段上涨、又不含任何下跌的亏损,既是收益上界也真能拿到。
和只能买卖一次的 LC121 差在哪?+
LC121 限一次交易,只能找历史最低点之后的一段最大涨幅;这题不限次数,可以把每一段上涨都吃下,所以改用贪心累加所有正差。同一串价格,一次交易往往少赚,因为它只框得住一段行情。
全程下跌或只有一天,会不会出错?+
不会。全程下跌时没有一天比前一天高,正差一个都加不上,profit 保持 0;只有一天时下标 i 从 1 起根本进不了循环,直接返回 0。两种边界都自然落在 0,不用额外特判。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 买卖股票 II 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。