LeetCode-122:买卖股票的最佳时机 II,贪心的本质是收集每一段上坡

本题在线练习LeetCode 122. 买卖股票的最佳时机 II — 在线练习(免费 · 无需登录 · AI 辅助)

配套刷题网站 Zero2Leetcode —— 内置本地 OJ + AI 教练,零门槛开刷 Hot 100。

题目概述

给定一个整数数组 prices,其中 prices[i] 表示某支股票第 i 天的价格。

你可以多次买卖(但任意时刻最多持有 1 股),求能获得的最大利润

例如:

  • 输入:prices = [7,1,5,3,6,4] → 输出:7
    • 第 2 天买入(价格 1),第 3 天卖出(价格 5),利润 4
    • 第 4 天买入(价格 3),第 5 天卖出(价格 6),利润 3
    • 总利润 = 4 + 3 = 7
  • 输入:prices = [1,2,3,4,5] → 输出:4
    • 第 1 天买,第 5 天卖,利润 4(等价于每天赚 1)
  • 输入:prices = [7,6,4,3,1] → 输出:0
    • 价格一路下跌,不做任何交易

核心思路:只要明天涨,今天就买

和 LC121(只能买卖一次)不同,这题允许无限次交易。关键洞察是:

任何一段"从谷底到山顶"的利润,都可以拆解成相邻两天的涨幅之和。

举个例子,prices = [1, 3, 5]

  • 一次交易赚 5 - 1 = 4
  • 拆成两天赚 (3-1) + (5-3) = 2 + 2 = 4

结果完全一样。这意味着:我们不需要真的去找谷底和山顶,只需要把所有相邻上涨的差值加起来就行了。

为什么这样做是最优的? 因为最优解中每一笔交易"买入第 a 天,卖出第 b 天"的利润 prices[b] - prices[a] 可以展开为:

prices[b] - prices[a] = (prices[a+1] - prices[a]) + (prices[a+2] - prices[a+1]) + ... + (prices[b] - prices[b-1])

其中有些差值是正的(涨了),有些是负的(跌了)。如果我们只收集正的差值,利润只会更多或相等,不会更少。而贪心恰好做的就是这件事:跳过所有下跌,收集所有上涨

所以贪心策略一定不差于任何最优方案,同时它自己也是可实现的(每段上涨都对应一次合法的买卖),因此它就是最优解。


代码

解法一:贪心(推荐)

from typing import List


class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        profit = 0
        for i in range(1, len(prices)):
            if prices[i] > prices[i - 1]:
                profit += prices[i] - prices[i - 1]
        return profit

解法二:动态规划(hold / not-hold 状态机)

贪心虽然简洁,但如果你在面试中不确定贪心的正确性,可以用 DP 稳稳地推导。

定义两个状态:

  • hold:第 i 天结束后持有股票时的最大利润
  • cash:第 i 天结束后不持有股票时的最大利润

转移方程:

  • hold = max(hold, cash - prices[i])(继续持有 or 今天买入)
  • cash = max(cash, hold + prices[i])(继续空仓 or 今天卖出)
from typing import List


class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        hold = -prices[0]  # 第 0 天买入
        cash = 0           # 第 0 天不操作

        for i in range(1, len(prices)):
            new_hold = max(hold, cash - prices[i])
            new_cash = max(cash, hold + prices[i])
            hold, cash = new_hold, new_cash

        return cash

逐行拆解

贪心解法

  • profit = 0
    • 初始利润为 0,还没有做任何交易
  • for i in range(1, len(prices))
    • 从第 2 天开始,每天和前一天比
  • if prices[i] > prices[i - 1]
    • 今天比昨天贵 = 这段是上坡
  • profit += prices[i] - prices[i - 1]
    • 把这段上坡的差价收入囊中

整个逻辑可以用一句话概括:只要涨了就赚,跌了就不动

DP 解法

  • hold = -prices[0]
    • 第 0 天如果持有股票,说明花了 prices[0] 买入,利润为 -prices[0]
  • cash = 0
    • 第 0 天如果不持有,利润为 0
  • new_hold = max(hold, cash - prices[i])
    • 要么延续昨天的持有状态,要么今天从空仓状态买入
  • new_cash = max(cash, hold + prices[i])
    • 要么延续昨天的空仓状态,要么今天卖出手中股票
  • new_holdnew_cash 暂存是为了避免同一天的更新互相干扰
  • 最终返回 cash,因为最后一天手里不持有股票才是最优的

手动模拟

prices = [7, 1, 5, 3, 6, 4] 走一遍贪心:

天数 i prices[i] prices[i]-prices[i-1] 是否收集 profit
1 1 1 - 7 = -6 否(下跌) 0
2 5 5 - 1 = +4 4
3 3 3 - 5 = -2 否(下跌) 4
4 6 6 - 3 = +3 7
5 4 4 - 6 = -2 否(下跌) 7

最终返回 7

再用 DP 走一遍同样的例子:

天数 i prices[i] hold cash
0 7 -7 0
1 1 max(-7, 0-1) = -1 max(0, -7+1) = 0
2 5 max(-1, 0-5) = -1 max(0, -1+5) = 4
3 3 max(-1, 4-3) = 1 max(4, -1+3) = 4
4 6 max(1, 4-6) = 1 max(4, 1+6) = 7
5 4 max(1, 7-4) = 3 max(7, 1+4) = 7

最终返回 cash = 7,和贪心结果一致。


复杂度分析

贪心解法:

  • 时间复杂度:O(n) -- 遍历一次数组
  • 空间复杂度:O(1) -- 只用了一个变量 profit

DP 解法:

  • 时间复杂度:O(n) -- 遍历一次数组
  • 空间复杂度:O(1) -- 只用了 holdcash 两个变量(状态压缩后)

总结

这道题的核心洞察:

多次交易的最大利润 = 所有相邻上涨的差价之和。不需要找谷底和山顶,逐天收集正差值就够了。

和 LC121 的对比:

LC121(一次交易) LC122(多次交易)
策略 维护历史最低价,计算当前卖出利润 收集所有正差值
核心变量 min_price, max_profit profit(累加)
DP 状态 同样是 hold/cash,但 hold 只能从初始状态买入 hold 可以从任意 cash 状态买入

贪心写法只有三四行,但如果面试官追问"怎么证明贪心是对的",记住关键论点:任意一笔交易的利润可以拆解为逐天差值之和,而贪心只收集正差值,所以不会比最优解差

如果对贪心没有把握,DP 状态机(hold/cash)是万能模板,可以平移到 LC121、LC123、LC188 等所有股票系列题目。

posted @ 2026-04-20 00:21  孤飞  阅读(72)  评论(0)    收藏  举报