买卖股票的最佳时机 图解题解
只买卖一次,怎么一眼锁定最佳时机?一个变量追着最低价跑就够了。
像在一张折线股价图上,从左往右拿着一根橡皮筋比价:橡皮筋左端始终钉在「扫过的最低点」,右端随每天价格移动——今天价格比最低点还低,就把左端重新钉到今天;否则拉一下橡皮筋量量今天能赚多少,顺手记录最大差价。整张图只过一遍,两个变量搞定。
这道题到底在问什么
- prices
- [7, 1, 5, 3, 6, 4]
- 输出
- 5 (第 1 天买 1,第 4 天卖 6)
先想最直接的笨办法
最直接是双重循环:枚举每个买入日 i、每个之后的卖出日 j,取 prices[j] − prices[i] 的最大值。一共约 n²/2 对,数据一大就超时。其实卖在第 j 天时,只关心 j 之前的最低买入价,根本不用回头逐个试。(动画第 3 步)
最优解:一步一步想明白
- 3最直接是双重循环:枚举每个买入日 i、每个之后的卖出日 j,取 prices[j] − prices[i] 的最大值。一共约 n²/2 对,数据一大就超时。其实卖在第 j 天时,只关心 j 之前的最低买入价,根本不用回头逐个试。
- 4关键招:从左往右扫一遍,维护一个 minP=「今天之前的历史最低价」。今天卖的利润就是 price − minP,一路取最大值。为什么对:卖在今天,最优的买入点一定是之前的最低价;而 minP 边走边更新,天然只看「今天以前」,自动保证买在卖之前。
- 5minP=∞, profit=0只需两个变量:minP 记历史最低价(开局设无穷大)、profit 记最大利润(开局 0)。
- 6minP=7, profit=0第 0 天:历史最低价先记成 7(紫色 l 标最低价位置),还没有可赚的差价,profit 仍 0。
- 7minP=1, profit=0价格 1 比历史最低还低——这天只更新 minP=1、先不卖(此刻卖反而亏)。最低价指针 l 移到这里,profit 保持 0。
- 8profit = 5−1 = 4今天价 5,用今天卖能赚 5 − minP = 5 − 1 = 4,刷新最大利润为 4。minP 仍是 1。
- 9profit 仍 4今天卖只赚 3 − 1 = 2,没超过已有的 4,最大利润不动。
- 10profit = 6−1 = 5核心一步:今天价 6,用历史最低价 1 买、今天卖能赚 6 − 1 = 5!刷新最大利润 5(绿色标出买点和卖点)。
- 11profit 仍 5今天卖只赚 4 − 1 = 3,不超过 5,最大利润不变。
- 12最大利润 = 5走完一遍,最大利润 5。全程只用 minP 和 profit 两个变量,扫一趟搞定。
- 15提醒:这题不是滑动窗口。滑窗要左右两个指针、靠窗口伸缩;这里只有一个游标从左扫到右,外加一个「历史最低价」标量。所谓「历史最值」就是「目前为止见过的最小/最大」,最大子数组和、接雨水都有它的影子。
- 20把这题练到能复述后,再去同类题里迁移:先复用「历史最值 + 当前元素算答案」这套状态,再调整 min/max 方向和算答案的公式。点开 AI 助教问问「LC53 怎么套这个骨架」。
⚠️ 容易写错的地方
✗ 错:minP 初值设成 0(或一个很小的数)
✓ 对:minP 初值设成无穷大(或 prices[0])
若 minP 一开局就是 0,「今天价 − minP」会被算成今天的整价,凭空多出根本买不到的利润;初值要设成无穷大,第一天才会被真实价格覆盖
✗ 错:profit 初值设成 -∞ 或很大的数
✓ 对:profit 初值设 0
题目允许不交易,价格全程下跌时没有正利润,应返回 0 而不是负数
✗ 错:记录买卖日下标后用 max(prices)−min(prices)
✓ 对:只维护「今天之前」的 minP
最高价可能出现在最低价之前(如本例 7 在 1 之前),全局最大减全局最小会算出无法实现的利润
完整代码(Python / C++ / Java)
Python
class Solution:
def maxProfit(self, prices):
low = 10**9
ans = 0
for price in prices:
low = min(low, price)
ans = max(ans, price - low)
return ansC++
class Solution {
public:
int maxProfit(vector<int>& prices) {
int low = INT_MAX, ans = 0;
for (int p : prices) { low = min(low, p); ans = max(ans, p - low); }
return ans;
}
};Java
class Solution {
public int maxProfit(int[] prices) {
int low = Integer.MAX_VALUE, ans = 0;
for (int p : prices) { low = Math.min(low, p); ans = Math.max(ans, p - low); }
return ans;
}
}套路模板 · 一遍扫描「维护历史最值」(背骨架,看题填空)
# 骨架:扫一遍,维护「历史最优」,再拿它和当前元素算答案
best = 0 # 答案(不操作也合法→初值常设 0)
extreme = float("inf") # 历史最优(求最小用 inf;求最大用 -inf)
for x in arr:
extreme = min(extreme, x) # 先更新历史最优
best = max(best, f(x, extreme)) # 再用「当前 + 历史最优」算答案
# 本题:f(x, extreme) = x - extreme// 骨架:扫一遍,维护「历史最优」,再拿它和当前元素算答案
long best = 0; // 答案(不操作也合法→初值常设 0)
long extreme = LONG_MAX; // 历史最优(求最小用 MAX;求最大用 MIN)
for (auto x : arr) {
extreme = min(extreme, x); // 先更新历史最优
best = max(best, f(x, extreme)); // 再用当前 + 历史最优算答案
}
// 本题:f(x, extreme) = x - extreme// 骨架:扫一遍,维护「历史最优」,再拿它和当前元素算答案
long best = 0; // 答案(不操作也合法→初值常设 0)
long extreme = Long.MAX_VALUE; // 历史最优(求最小用 MAX;求最大用 MIN)
for (int x : arr) {
extreme = Math.min(extreme, x); // 先更新历史最优
best = Math.max(best, f(x, extreme)); // 再用当前 + 历史最优算答案
}
// 本题:f(x, extreme) = x - extreme复杂度
时间复杂度
O(n)
只扫一遍价格数组,每个元素常数次操作
空间复杂度
O(1)
只用 minP、profit 两个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 买卖股票的最佳时机 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么不是「全局最大 − 全局最小」?+
必须先买后卖,卖出价要在买入价之后。所以维护「到当前为止的最低价」,再算今天卖出的利润。
能多次交易(LC122)怎么变?+
贪心累加所有上涨段;本题只能一次,取单段最大涨幅。
这算滑动窗口吗?+
不算。滑窗要左右双指针、窗口伸缩;这题只有一个游标单向走,外加一个历史最低价标量,是「一遍扫描维护最值」。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。