买卖股票的最佳时机 图解题解
只买卖一次,怎么一眼锁定最佳时机?一个变量追着最低价跑就够了。
像在一张折线股价图上,从左往右拿着一根橡皮筋比价:橡皮筋左端始终钉在「扫过的最低点」,右端随每天价格移动——今天价格比最低点还低,就把左端重新钉到今天;否则拉一下橡皮筋量量今天能赚多少,顺手记录最大差价。整张图只过一遍,两个变量搞定。
这道题到底在问什么
- 输入
- prices=[7,1,5,3,6,4]
- 输出
- 5 (第2天价1买入、第5天价6卖出,赚 6−1 = 5)
最优解:为什么这么做
一句话答案:LeetCode 121 买卖股票的最佳时机的最优解是一次遍历:从左到右扫价格数组,边走边维护「历史最低买入价」,对每一天假设今天卖出、用当天价减历史最低价更新最大利润。每天只做两次比较,时间 O(n)、空间 O(1),天然保证卖出在买入之后。
这道题真正在问什么
题目给一个数组 prices,prices[i] 是第 i 天的股价,只允许完成一笔交易——买一次、卖一次,且卖出必须在买入之后。求能拿到的最大利润,如果怎么交易都亏,就返回 0。隐藏的约束有两条:第一,交易只有一笔,不能反复低买高卖;第二,时间有方向,「卖在买之后」这条顺序限制正是本题和「找数组最大值减最小值」的本质区别。
为什么不能用全局最高价减全局最低价
一个诱人的错误想法是:最大利润不就是数组里的最大值减最小值吗?反例很快能构造出来——如果最高价出现在最低价之前,比如价格先冲到 9 再跌到 1,那这笔「先卖后买」的交易根本不合法。差值必须带方向:只能拿某一天的价格,去减它之前(含当天)出现过的最低价。
另一个直觉做法是两层循环,枚举所有买入日和卖出日的组合,取差值最大的一对。它是对的,但 n 天要试大约 n²/2 对,时间复杂度 O(n²),价格数组一长就慢得不可接受。
关键观察:固定卖出日,最优买入日是确定的
换个角度想:假如已经决定第 i 天卖出,那买入日该选哪天?答案是唯一的——第 0 到第 i 天里价格最低的那天,因为卖价固定时,买得越便宜赚得越多。这样问题就从「枚举所有买卖组合」坍缩成「对每个卖出日,查一下它之前的最低价」。
而「之前的最低价」这个量,恰好可以在从左到右扫描时顺手维护:走到第 i 天时,用当天价格去更新一个变量 min_price,它就始终等于前 i 天的最低价,也就是所谓的前缀最小值。原来内层循环要做的整段查找,被压缩成一次比较。
一次遍历的每一步为什么成立
算法保持两个不变量:min_price 永远是「到目前为止见过的最低价」,best 永远是「到目前为止能实现的最大利润」。每到新的一天,先用当天价更新 min_price,再计算「假设今天卖」的利润 p - min_price 去更新 best。因为 min_price 只由今天及之前的价格构成,用它算出的每笔利润都自动满足「先买后卖」,不需要额外检查顺序。
有人会担心:先更新 min_price 再算利润,万一今天恰好是新低,算出的利润不就是 0 吗?没关系——今天买今天卖利润本来就是 0,不会污染 best;而真正的收益要等之后某天价格高于这个新低时才兑现,届时自然会被更新进去。
复杂度怎么算,哪些边界会翻车
时间 O(n):数组只扫一遍,每天做一次取最小、一次取最大,都是常数操作。空间 O(1):全程只用 min_price 和 best 两个变量,不需要任何额外数组。
两个真会翻车的边界:一是价格一路下跌时,任何交易都亏,正确答案是 0 而不是负数——把 best 初始化为 0、亏了就当不交易,这条就自动满足;二是别把本题和 LeetCode 122 混淆,122 允许多次买卖、贪心吃下每段上涨即可,而本题只许一笔交易,找的是历史最低点之后的最大单段涨幅。
▶ 动画逐步走查(共 26 步)——想跟着动画一帧帧对照就展开
- 3记住这条「维护历史最低买价、每天假设今天卖、更新最大利润」,下面每一天都在套它。
- 4第 0 天价 7,比之前见过的都便宜,刷新历史最低买点(绿色格子移到这里)。买点越低,后面越可能赚得多。
- 5假设今天卖:7 − 7 = 0,不如已有的最大利润 0,保持不变。
- 6第 1 天价 1,比之前见过的都便宜,刷新历史最低买点(绿色格子移到这里)。买点越低,后面越可能赚得多。
- 7假设今天卖:1 − 1 = 0,不如已有的最大利润 0,保持不变。
- 8第 2 天价 5,没有比历史最低 1(第 1 天)更便宜,买点不动,绿色仍停在第 1 天。
- 9假设今天卖:5 − 1 = 4,比旧的最大利润 0 更高,刷新最大利润为 4(第 1 天买、第 2 天卖)。
- 10第 3 天价 3,没有比历史最低 1(第 1 天)更便宜,买点不动,绿色仍停在第 1 天。
- 11假设今天卖:3 − 1 = 2,不如已有的最大利润 4,保持不变。
- 12第 4 天价 6,没有比历史最低 1(第 1 天)更便宜,买点不动,绿色仍停在第 1 天。
- 13假设今天卖:6 − 1 = 5,比旧的最大利润 4 更高,刷新最大利润为 5(第 1 天买、第 4 天卖)。
- 14第 5 天价 4,没有比历史最低 1(第 1 天)更便宜,买点不动,绿色仍停在第 1 天。
- 15假设今天卖:4 − 1 = 3,不如已有的最大利润 5,保持不变。
- 16第 6 天价 2,没有比历史最低 1(第 1 天)更便宜,买点不动,绿色仍停在第 1 天。
- 17假设今天卖:2 − 1 = 1,不如已有的最大利润 5,保持不变。
- 18第 7 天价 8,没有比历史最低 1(第 1 天)更便宜,买点不动,绿色仍停在第 1 天。
- 19假设今天卖:8 − 1 = 7,比旧的最大利润 5 更高,刷新最大利润为 7(第 1 天买、第 7 天卖)。
- 20第 8 天价 1,没有比历史最低 1(第 1 天)更便宜,买点不动,绿色仍停在第 1 天。
- 21假设今天卖:1 − 1 = 0,不如已有的最大利润 7,保持不变。
- 22第 9 天价 9,没有比历史最低 1(第 1 天)更便宜,买点不动,绿色仍停在第 1 天。
- 23假设今天卖:9 − 1 = 8,比旧的最大利润 7 更高,刷新最大利润为 8(第 1 天买、第 9 天卖)。
- 24第 10 天价 4,没有比历史最低 1(第 1 天)更便宜,买点不动,绿色仍停在第 1 天。
- 25假设今天卖:4 − 1 = 3,不如已有的最大利润 8,保持不变。
- 26第 11 天价 10,没有比历史最低 1(第 1 天)更便宜,买点不动,绿色仍停在第 1 天。
- 27假设今天卖:10 − 1 = 9,比旧的最大利润 8 更高,刷新最大利润为 9(第 1 天买、第 11 天卖)。
- 28扫完全程,最优是第 1 天(价 1)买、第 11 天(价 10)卖,赚 9。注意卖点(11) 在买点(1) 之后——这正是「历史最低价只看今天及之前」自动保证的。
⚠️ 容易写错的地方
✗ 错:用全局最高价 − 全局最低价
✓ 对:卖必须在买之后,得维护「历史」最低价
若最高价出现在最低价之前,那笔交易不合法(不能先卖后买)
✗ 错:先固定买点再找卖点(双层循环)
✓ 对:一次遍历边走边更新最低买价即可
双层是 O(n²);记历史最低价让买点自动取到当前最优
✗ 错:一路下跌时返回负数
✓ 对:最大利润初始为 0,亏就不交易
不交易利润为 0,题目要求无法获利返回 0
完整代码(Python / C++ / Java)
Python
def maxProfit(prices):
min_price = float("inf") # 历史最低买入价
best = 0 # 最大利润
for p in prices:
min_price = min(min_price, p) # 更新最低买点
best = max(best, p - min_price) # 今天卖的利润
return bestC++
int maxProfit(vector<int>& prices){
int minPrice = INT_MAX, best = 0;
for(int p : prices){
minPrice = min(minPrice, p); // 历史最低买价
best = max(best, p - minPrice); // 今天卖的利润
}
return best;
}Java
public int maxProfit(int[] prices) {
int minPrice = Integer.MAX_VALUE, best = 0;
for (int p : prices) {
minPrice = Math.min(minPrice, p); // 历史最低买价
best = Math.max(best, p - minPrice); // 今天卖的利润
}
return best;
}复杂度
时间
O(n)
从头到尾扫一遍
空间
O(1)
只用最低价、最大利润两个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 买卖股票的最佳时机 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么一次遍历就够,不用枚举所有买卖对?+
固定卖点 i 时,最优买点一定是 prices[0..i] 的最小值。我们边扫边维护这个前缀最小值 minPrice,于是每个卖点只需 O(1) 算 prices[i]−minPrice,全程 O(n),无需 O(n²) 枚举。
和「可多次买卖」(LC122) 有什么区别?+
LC121 只能交易一次,要找「历史最低点之后的最大单段涨幅」,用维护最低价 + 最大利润;LC122 不限次数,把每一段上涨都吃下,用贪心累加所有相邻正差价。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 买卖股票的最佳时机 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。