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]
- 第 0 天如果持有股票,说明花了
cash = 0- 第 0 天如果不持有,利润为 0
new_hold = max(hold, cash - prices[i])- 要么延续昨天的持有状态,要么今天从空仓状态买入
new_cash = max(cash, hold + prices[i])- 要么延续昨天的空仓状态,要么今天卖出手中股票
- 用
new_hold和new_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)-- 只用了hold和cash两个变量(状态压缩后)
总结
这道题的核心洞察:
多次交易的最大利润 = 所有相邻上涨的差价之和。不需要找谷底和山顶,逐天收集正差值就够了。
和 LC121 的对比:
| LC121(一次交易) | LC122(多次交易) | |
|---|---|---|
| 策略 | 维护历史最低价,计算当前卖出利润 | 收集所有正差值 |
| 核心变量 | min_price, max_profit |
profit(累加) |
| DP 状态 | 同样是 hold/cash,但 hold 只能从初始状态买入 | hold 可以从任意 cash 状态买入 |
贪心写法只有三四行,但如果面试官追问"怎么证明贪心是对的",记住关键论点:任意一笔交易的利润可以拆解为逐天差值之和,而贪心只收集正差值,所以不会比最优解差。
如果对贪心没有把握,DP 状态机(hold/cash)是万能模板,可以平移到 LC121、LC123、LC188 等所有股票系列题目。

浙公网安备 33010602011771号