题目描述
可以多次买入卖出(手里最多只持一股),求能拿到的最大总利润。
思路解析
最直接的念头是「找一个谷底买进、找一个峰顶卖出」。但能交易很多次,到底切成几段、每段谷峰在哪,枚举起来又乱又慢,而且根本没必要知道具体的买卖点。
换个角度:把利润拆到每一天。今天比昨天涨,就把这点差价收进口袋(相当于昨天买、今天卖);今天比昨天跌,就空仓什么都不做(加 0)。为什么成立:一段连续上涨 1→5 的总收益 4,正好等于它每天小涨之和;所以「把所有正的日差累加」和「整段买卖」收益完全相等,而且不限次数让我们可以自由分段——逐日贪心就是全局最优。
准备 · 从第 2 天开始:利润 profit 从 0 起。第 1 天(价 7)没有前一天可比,cur 先停在这里,真正的累加从第 2 天开始。
第 2 天 · 7 → 1 算差:走到第 2 天(价 1),先算今天减昨天:1 − 7 = −6,是负的。
第 2 天 · 跌 → 加 0:日差是负的,这天不操作(空仓),profit 加 0、保持 0。这就是负例:下跌日一律跳过,灰掉表示没吃进。
第 3 天 · 1 → 5 算差:走到第 3 天(价 5),算差:5 − 1 = +4,第一次出现正的涨幅。
第 3 天 · 涨 → 吃下:日差为正,把这 4 块吃进口袋:profit 累计到 4。绿色标出的 1 和 5 就是这一段「昨天买、今天卖」赚到的钱。
第 4 天 · 5 → 3 算差:走到第 4 天(价 3),算差:3 − 5 = −2,又是负的。
第 4 天 · 跌 → 加 0:下跌日再次跳过,profit 不动,仍是 4。注意这里没有去「找谷底找峰顶」——只看相邻两天的正负就够了。
第 5 天 · 3 → 6 算差:走到第 5 天(价 6),算差:6 − 3 = +3,又一段上涨。
第 5 天 · 涨 → 吃下:正差再吃下 3 块:profit 从 4 累到 7。绿色的 3 和 6 是这第二段赚到的钱。
第 6 天 · 6 → 4 收尾:最后一天 6 → 4 又是跌,跳过。全程只把两段正涨幅累加:4 + 3 = 7,就是答案。
一段连续上涨(如 1→5)拆成每天的差,求和不变;下跌日加 0 不亏。所以「每个正的日差都拿」就等于全局最优——这就是贪心在这题成立的根本原因。
参考代码(Python)
1def maxProfit(self, prices):
2 profit = 0
3 for i in range(1, len(prices)): # 从第 2 天起逐天比
4 if prices[i] > prices[i - 1]: # 今天比昨天涨
5 profit += prices[i] - prices[i - 1] # 吃下这段涨幅
6 return profit复杂度分析
- 时间复杂度:O(n) —— 只从第 2 天到最后扫一遍,每天做一次比较
- 空间复杂度:O(1) —— 只用 profit 一个变量,不开任何数组
套路模板
记住骨架:相邻两步算增量、只累加正的。前提是「次数不限、各段收益互不影响」——否则(如只能交易一次)不能这么贪。
1# 「操作次数不限、收益可分段独立」的贪心都套
2total = 0
3for i in range(1, n):
4 gain = 收益(i - 1, i)
5 if gain > 0: total += gain # 只累加正收益
6return total易错点
- 错误写法:费劲去找全局最低点买、最高点卖 → 正确写法:只累加每段正的日差 max(0, 今−昨)(不限次数时根本不用知道具体谷峰,分段累加正涨幅就是最优,找谷峰反而又慢又容易切错段)
- 错误写法:把这套累加直接用在「只能交易一次」的 LC121 → 正确写法:一次交易要维护历史最低价 minPrice 求最大单段差(次数受限时利润不可分段独立累加,简单加正差会偏大)
- 错误写法:for i in range(len(prices)) 从 0 开始 → 正确写法:for i in range(1, len(prices))(第 1 天没有「昨天」,从 0 起会越界访问 prices[-1])
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。