121. 买卖股票的最佳时机

简单 含交互动画

学完这道你应该能: 说清它为什么用「一次遍历」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

AI 私教 · 就着本题动画讲
卡住了不用硬扛,按 8 步把这题真正走通。
从读题、试解、找法到手写代码,边问边打勾,适合第一次系统学算法的同学。

题目描述

只能选某天买入、之后某天卖出(各一次),求最大利润;不交易则利润为 0。

prices = [7, 1, 5, 3, 6, 4]
输出 = 5 (第 1 天买 1,第 4 天卖 6)

思路解析

最直接是双重循环:枚举每个买入日 i、每个之后的卖出日 j,取 prices[j] − prices[i] 的最大值。一共约 n²/2 对,数据一大就超时。其实卖在第 j 天时,只关心 j 之前的最低买入价,根本不用回头逐个试。

关键招:从左往右扫,维护一个 minP=「今天之前的历史最低价」。今天卖的利润就是 price − minP,一路取最大值。为什么对:卖在今天,最优的买入点一定是之前的最低价;而 minP 边走边更新,天然只看「今天以前」,自动保证买在卖之前。

两个变量就位:只需两个变量:minP 记历史最低价(开局设无穷大)、profit 记最大利润(开局 0)。

第 0 天 · 价 7:第 0 天,历史最低价更新为 7(紫色 l 标最低价位置)。今天买今天卖赚 0,利润不变。

第 1 天 · 价 1 → 只更新最低价:负例分支:价格 1 比历史最低还低——这天只更新 minP=1、不卖(此刻卖反而亏)。最低价指针 l 移到这里,profit 保持 0。

第 2 天 · 价 5 → 第一次能赚:今天价 5,用今天卖能赚 5 − minP = 5 − 1 = 4,刷新最大利润为 4。minP 仍是 1。

第 3 天 · 价 3 → 没超过:今天卖只赚 3 − 1 = 2,没超过已有的 4,最大利润不动。

第 4 天 · 价 6 → 刷新最大:核心一步:今天价 6,用历史最低价 1 买、今天卖能赚 6 − 1 = 5!刷新最大利润 5(绿色标出买点和卖点)。

第 5 天 · 价 4 → 没超过:今天卖只赚 4 − 1 = 3,不超过 5,最大利润不变。

结束:走完一遍,最大利润 5。全程只用 minP 和 profit 两个变量,扫一趟搞定。

「一遍扫描里维护一个目前为止的最值」是数组题超常用的套路:最大子数组和、接雨水都有它的影子。

参考代码(Python)

Python
1def maxProfit(prices):
2    minP = float(class="cl-str">"inf")
3    profit = 0
4    for p in prices:
5        minP = min(minP, p)            # 先更新历史最低买入价
6        profit = max(profit, p - minP) # 再算今天卖能赚多少
7    return profit

复杂度分析

  • 时间复杂度:O(n) —— 只扫一遍价格数组
  • 空间复杂度:O(1) —— 只用 minP、profit 两个变量

套路模板

一遍扫描里维护「历史最值」,再用它和当前元素算答案——很多一维最优问题都能套这个骨架。

Python
1# 维护「历史最优」,再拿它和当前元素算答案
2best = 0; extreme = float(class="cl-str">"inf")
3for x in arr:
4    extreme = min(extreme, x)      # 历史最优(看题改 min / max)
5    best = max(best, x - extreme)  # 用当前元素和历史最优算答案

易错点

  • 错误写法:先算利润 profit,再更新 minP正确写法:先更新 minP,再用它算 profit(顺序反了会用「包含今天的最低价」去买、又在今天卖,等于同一天买卖;先更新 minP 才保证买入价来自今天之前(或今天),逻辑才对)
  • 错误写法:profit 初值设成 -∞ 或很大的数正确写法:profit 初值设 0(题目允许不交易,价格全程下跌时没有正利润,应返回 0 而不是负数)
  • 错误写法:记录买卖日下标后用 max(prices)−min(prices)正确写法:只维护「今天之前」的 minP(最高价可能出现在最低价之前(如本例 7 在 1 之前),全局最大减全局最小会算出无法实现的利润)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →134. 加油站 ← 返回题库