60 秒回答模板

只能买卖一次时,本质是在数组中找一对下标 i < j,让 prices[j] - prices[i] 最大。遍历价格时维护到当前为止的最低价格 minPrice,把当前价格减 minPrice 得到如果今天卖出的利润,并更新最大值。然后再用当前价格更新 minPrice。这样一趟扫描就能完成,时间复杂度 O(n),空间 O(1)。面试时要说明不能先卖后买、价格单调下降时利润通常返回 0,以及它和无限次、两次买卖的状态转移不同。

考点 历史最低价
难度 真实面经题
回答目标 讲清方法、边界和追问

深入解析

01

先明确交易约束

题目限制只能买卖一次,意味着只能选择一个买入点和一个更晚的卖出点。不能把多个上涨区间累加,也不能出现先卖后买的下标顺序错误。

02

维护历史最低价

遍历到第 k 天时,最优买入价只能来自前 k 天已经见过的最低价格。用一个变量 minPrice 记录它,就不需要枚举所有买入日。

03

每天尝试作为卖出日

当前价格 price 减去 minPrice,就是如果今天卖出的最大可能利润。用它更新 maxProfit,再把 minPrice 更新成当前和历史最低的较小值。

04

复杂度和边界

算法只扫描一次数组,时间复杂度 O(n),额外空间 O(1)。空数组、单个价格或单调下降时没有正收益,通常返回 0。

05

区分其他变体

无限次买卖可以累加所有正收益,两次买卖需要前后缀或状态机。一次买卖题不要过度设计成多状态 DP,但要能解释这些变体的差异。

javascript

维护历史最低价的一次扫描

function maxProfitOnce(prices) {
  let minPrice = Infinity;
  let maxProfit = 0;

  for (const price of prices) {
    maxProfit = Math.max(maxProfit, price - minPrice);
    minPrice = Math.min(minPrice, price);
  }

  return maxProfit;
}

maxProfitOnce([1, 3, 1, 8, 10, 3]); // 9
  • 先用当前价格尝试卖出,再更新最低价,可以保证买入日早于卖出日。
  • 单调下降时保持 0,表示不交易优于亏损交易。

易错点

  • 不要用当前之后的最低价作为买入价,那会违反时间顺序。
  • 不要把所有上涨段累加到一次买卖题里,那是无限次交易的做法。
  • 不要忘记单调下降和数组长度不足两个边界。

面试官追问

如果价格一直下降怎么办?

每次当前价格减历史最低价都不为正,maxProfit 保持 0,表示不交易更优。

无限次买卖怎么做?

只要后一天比前一天高,就累加差值,相当于吃掉所有上涨区间。

只能买卖两次怎么做?

可以用四个状态表示第一次买入、第一次卖出、第二次买入、第二次卖出的最优收益,逐日转移。