D

当前:LC121 · 买卖股票的最佳时机 · 首次出现于 Day 40 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC121

最低价雷达 · Best Time to Buy and Sell Stock

一次遍历 · 贪心

不是去预测未来,而是每天记住之前见过的最低买入价,再试算「今天卖出能赚多少」,留下历史最大利润就是答案。

输入 · prices
715364
Day 1 买入 Day 4 卖出
最大利润
+5
一次买 + 一次卖 · 买入必须早于卖出
时间 O(n) · 一次遍历空间 O(1) · 两个变量关键不变量:minPrice 单调不增 / bestProfit 单调不降
初始化Step 1 / 6

价格雷达 · 扫描每一天

扫描中:Day 0 · price 7
BUY ANCHOR
7
Day 0
1
Day 1
5
Day 2
3
Day 3
6
Day 4
4
Day 5
Day 0 开盘:先把 minPrice 设成 7,profit 设成 0。还没有交易过,没有利润可谈。
金色 · 今日扫描蓝色 · BUY ANCHOR · 历史最低买价绿色 · BEST SELL · 形成最大利润的卖点灰色 · 已扫描过的天

Go 解法 · 与状态同步

high-lighted: 2, 3
1func maxProfit(prices []int) int {
2 minPrice := prices[0]
3 profit := 0
4 for i := 1; i < len(prices); i++ {
5 if prices[i] < minPrice {
6 minPrice = prices[i]
7 }
8 if prices[i]-minPrice > profit {
9 profit = prices[i] - minPrice
10 }
11 }
12 return profit
13}

蓝色行 = 更新 minPrice · 绿色行 = 刷新 profit · 金色 = 当前正在扫描

状态面板 · 这一帧的全部状态

当前天
Day 0
当前价格
7
minPrice
7
candidate = price − minPrice
+0
bestProfit
+0
本步动作
初始化

不变量

  • minPrice = 扫描至今见过的最低买入价
    只要今天的价格比 minPrice 更低,就刷新它。否则保持不变。它只会变小,不会变大。
  • bestProfit = 扫描至今能拿到的最大利润
    每天都问一句『如果今天卖出,能赚多少?』把答案和 bestProfit 比一下,谁大谁留下。它只会变大,不会变小。
  • 每一天只需回答两个小问题
    ① 今天有没有刷新最低买价? ② 今天卖出,是否比之前更赚?两个判断之外什么都不做。

执行轨迹 · 每一帧的 before / after

点击行跳转
FrameDaypriceminPrice (before → after)candidatebestProfit (before → after)动作
00777+000初始化
11171+000更新历史最低买价
22511+404刷新最大利润
33311+244保持现有最大利润
44611+545刷新最大利润
55411+355保持现有最大利润

面试 30 秒答题模板

这道题只允许一次买卖,且买入必须早于卖出,所以我做 一次遍历: 从第 0 天开始,维护一个 历史最低买价 minPrice;从第 1 天开始,每一天先看是否要把 minPrice 刷新得更低,再 试算 prices[i] − minPrice 作为「今天就卖出能拿到的利润」,并和已有的最大利润比较,谁大留谁。 遍历完成后返回 bestProfit。 时间 O(n)、空间 O(1)。 正确性来自两条不变量:minPrice 永远是扫描至今的最低买价; bestProfit 永远是扫描至今的最大可得利润,因此整段扫描结束时 bestProfit 就是全局最优。

常见误区 · 别再翻车

  • 把 LC121 当 LC122
    LC121 只允许一次买卖;LC122 才允许多次交易(捕捉所有上升段)。一上来累加涨幅就跑偏了。
  • 用全局 max(prices) − min(prices)
    不行。买入必须早于卖出,全局最大值可能出现在最低价之前,那笔交易是非法的。
  • 以为算法在预测未来
    并没有。每一天的 minPrice 只来自已经扫描过的天,永远不偷看后面的价格。
  • 把 minPrice 当成全局最低价
    minPrice 是动态维护的状态:在 Day i 这一刻,它只代表 prices[0..i] 的最低价,而不是整段数组的最低价。