121. 买卖股票的最佳时机 (Easy)

专题归类: 11-贪心 LeetCode 链接: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/


在线做题: 写 Python 代码并运行 · Java 跳转 LeetCode

题目描述

给定一个数组 prices,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票,设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:5
解释:在第 2 天(价格 = 1)的时候买入,在第 5 天(价格 = 6)的时候卖出,最大利润 = 6 - 1 = 5。

示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下,没有交易完成,所以最大利润为 0。

题目详细分析

数据范围: 1 <= prices.length <= 10^50 <= prices[i] <= 10^4

核心约束:

  • 只能买卖一次(一次买入一次卖出),且必须先买后卖
  • 买入必须在卖出之前(即卖出的下标 > 买入的下标)
  • 若价格一直下跌,最大利润为 0(不交易)

关键洞察:

  • 这是一个单次交易问题,相当于在历史最低点买入,未来最高点卖出
  • O(n) 遍历即可,无需使用 O(n^2) 的暴力法
  • 因为只能交易一次,局部最优(每天记录历史最低价)就是全局最优,这是贪心能用的根本原因

小白版直白理解

就像你逛街想买一件东西,你知道它未来几天的价格。你只能买一次、卖一次。那你肯定会想:在价格最低的那天买入,然后在之后价格最高的那天卖出。但如果价格一直跌,那就不买,利润为 0。

换种说法:你每天记录下”到今天为止的最低价格”,然后每天问自己:“如果我今天卖出,能赚多少钱?” 把每天算出的最大利润记下来,取最大的那个就是答案。


解题思路

思路一:贪心 / 一次遍历(推荐)

思路讲解: 核心思想是在遍历过程中维护两个变量:

  • min_price:遍历到当前位置时的最低价格(历史最低价)
  • max_profit:到当前位置时能获得的最大利润

每遍历一天,先用当天价格更新 min_price(保证买入价最低),再计算”如果今天卖出”的利润 price - min_price,用它更新 max_profit。这就是贪心思想的体现——在每一天都做局部最优决策(找历史最低买入价),最终得到全局最优(最大利润)。

def maxProfit(prices):
    min_price = float('inf')
    max_profit = 0
    for price in prices:
        # 更新历史最低买入价
        if price < min_price:
            min_price = price
        # 计算今天卖出能赚多少,更新最大利润
        elif price - min_price > max_profit:
            max_profit = price - min_price
    return max_profit

时间复杂度: O(n) | 空间复杂度: O(1)

思路二:动态规划

思路讲解: 用两个状态表示第 i 天的最大收益:

  • dp0:第 i 天不持有股票时的最大现金(即已卖出或不买入)
  • dp1:第 i 天持有股票时的最大现金(即已买入)

初始化:dp0 = 0(没买),dp1 = -prices[0](第一天买入花掉 prices[0]) 转移:dp0 = max(dp0, dp1 + price)(今天卖出或不卖),dp1 = max(dp1, -price)(今天买入或不买,注意只能买一次所以是 -price

def maxProfit(prices):
    dp0 = 0          # 不持有股票的最大现金
    dp1 = -prices[0] # 持有股票的最大现金(负值表示成本)
    for price in prices[1:]:
        dp0 = max(dp0, dp1 + price)  # 卖出 or 不动
        dp1 = max(dp1, -price)       # 买入 or 不动(只能买一次)
    return dp0

时间复杂度: O(n) | 空间复杂度: O(1)


易错点

  • 数组长度为 1:只能买入无法卖出,利润为 0,代码应能正确处理
  • 价格一直下跌:不交易利润为 0,不要返回负数
  • 先买后卖顺序:不能先卖后买(不能做空),利润 = 后面价格 - 前面价格必须非负才能交易
  • 初始值设置min_price 初始化为 float('inf') 而非 prices[0],这样第一个元素就能正确更新
  • DP 写法中 dp1 的更新:只能用 -price 而非 dp1(因为只能买一次),如果允许多次买卖则用 dp0 - price

框架提炼

贪心模板:维护历史最值

def solve(prices):
    best_buy = INF        # 维护历史最优买入条件
    best_profit = 0       # 维护历史最优结果
    for x in prices:
        best_buy = min(best_buy, x)          # 更新"买入"条件
        best_profit = max(best_profit, x - best_buy)  # 更新结果
    return best_profit

这类”一次交易”问题都可以套用此模板——维护一个历史最值,然后在遍历中持续更新答案。

DP 框架(状态机):

dp0, dp1 = 0, -inf
for price in prices:
    dp0 = max(dp0, dp1 + price)   # 不持股:之前就不持股 / 今天卖出
    dp1 = max(dp1, -price)        # 持股:之前就持股 / 今天买入

这个 DP 框架可以推广到”可以交易 k 次”的通用股票问题。


关联题目