引言
——深度解析股票交易三题的核心思想与思维跃迁
当你盯着屏幕上红绿交错、上蹿下跳的K线图时,是否想过:那些让散户抓耳挠腮的买卖时机问题,其实早已被算法精准解构?LeetCode 上的三道经典股票交易题(121、122、123)不仅是面试高频题,更是一场关于选择、策略与思维方式的思维训练。
这三道题层层递进,从"只能买卖一次"到"无限次交易",再到"最多两次交易",背后藏着两种核心算法范式:贪心算法与动态规划。它们不只是代码技巧,更是应对复杂决策的智慧模型。
本文将不再满足于"给出代码 + 简单解释",而是深入剖析每道题的本质结构、关键洞察点和思维跃迁过程,帮助你真正理解:"为什么这个思路成立?"、"为什么非得这么设计状态?"、"能不能推广到更多次交易?"
一、LeetCode 121:只能买卖一次 —— "人生没有后悔药"
📌 题目重述:
给定一个数组 prices,prices[i] 表示第 i 天的股价。你最多只能完成一笔交易(即买一次、卖一次),求最大利润。
示例:
[7,1,5,3,6,4]→ 最大利润为 5(第2天买,第5天卖)
❌ 常见误区:暴力枚举所有组合
最直观的想法是双重循环,枚举所有 i < j 的 (buy, sell) 组合,计算差值取最大。
时间复杂度 O(n²),虽然能过小数据,但在面试中几乎等于"出局"。
✅ 正确打开方式:逆向思维 + 贪心维护历史最优
让我们换一个角度思考:
"如果我在第
i天卖出,那么为了利润最大,我应该在哪一天买入?"
答案很明确:在第 i 天之前价格最低的那一天买入。
也就是说,对于每一个"卖出日" ,我们都希望找到它前面的"最低买入价"。
于是我们可以边遍历数组,边维护两个变量:
遍历每一天的价格 price:
- 更新
minPrice = Math.min(minPrice, price) - 计算"今天卖出"的利润:
profit = price - minPrice - 更新
maxProfit = Math.max(maxProfit, profit)
这样,我们在一次遍历中就完成了全局最优解的搜索。
💡 关键洞察点:
这个问题的本质是:在一个序列中找一对索引 (i, j),满足 i < j,使得 prices[j] - prices[i] 最大。
这等价于求"最大后缀差"。而贪心策略巧妙地将其转化为一个在线更新问题,避免了回溯或预处理。
| 天数 | 价格 | minPrice | 当天利润 | maxProfit |
|---|---|---|---|---|
| 0 | 7 | 7 | - | 0 |
| 1 | 1 | 1 | 0 | 0 |
| 2 | 5 | 1 | 4 | 4 |
| 3 | 3 | 1 | 2 | 4 |
| 4 | 6 | 1 | 5 | 5 |
| 5 | 4 | 1 | 3 | 5 |
可以看到,第4天(价格6)时,我们找到了最大利润5(6-1)。
🧠 深层理解:
- 我们不需要知道未来的价格,只需要基于已知信息做出当前最优决策。
- 这正是贪心算法的精髓:局部最优可以推出全局最优。
- 成立的前提是:只能交易一次 → 决策具有无后效性,且目标函数可分解。
✅ 时间复杂度:O(n)
✅ 空间复杂度:O(1)
🌰 代码实现:
这段代码的精妙之处在于:它用最简单的两个变量,完成了对整个交易过程的建模。我们不需要知道"什么时候买入",只需要知道"当前最低价是多少";我们也不需要知道"什么时候卖出",只需要知道"如果今天卖出能赚多少"。
二、LeetCode 122:可以无限次买卖 —— "积小胜为大胜"
📌 题目重述:
允许进行任意多次交易(但不能同时持有多股,必须先卖后买),求最大总利润。
示例:
[7,1,5,3,6,4]→ 可以 1买5卖(+4),3买6卖(+3),总利润 7
❌ 直觉陷阱:找所有上升波段?
有人会想:"那我就找出所有的上升区间,比如 [1→5]、[3→6],然后分别操作。"
听起来合理,但实现起来需要判断何时开始/结束一段上涨,容易出错。
✅ 更优雅的贪心视角:把每一次正向波动都当作机会
关键洞察来了:
任何一次价格上涨,都可以被捕捉为利润!
比如:
- 第2天到第3天:1 → 5,涨了4 → 利润+4
- 第3天到第4天:5 → 3,跌了 → 不操作
- 第4天到第5天:3 → 6,涨了3 → 利润+3
你会发现,只要把所有 prices[i] > prices[i-1] 的差值加起来,结果就是最大利润!
| 天数 | 价格 | 与前一天差值 | 是否累加 | 累计利润 |
|---|---|---|---|---|
| 0 | 7 | - | - | 0 |
| 1 | 1 | -6 | ❌ | 0 |
| 2 | 5 | +4 | ✅ | 4 |
| 3 | 3 | -2 | ❌ | 4 |
| 4 | 6 | +3 | ✅ | 7 |
| 5 | 4 | -2 | ❌ | 7 |
可以看到,我们累加了两次上涨(4和3),总利润为7。
💡 为什么这是对的?
数学上可以证明:
多次交易的最大利润 = 所有上升区间的总和
为什么?因为你可以"虚拟持有":
- 如果明天会涨,今天就买入;
- 如果明天会跌,今天就卖出;
虽然现实中无法预知未来,但在算法中,这种"差分累加"等价于你在每个上涨日都完成了"低买高卖"。
🎯 举个例子:
[1,3,5]
- 差分法:(3-1)+(5-3)=2+2=4
- 实际操作:1买5卖,利润4 → 完全一致!
这说明:即使不允许"当天买卖",这种策略也能通过连续操作实现相同效果。
🧠 思维跃迁:
- 不再关注"什么时候买入/卖出",而是关注"趋势方向"。
- 将复杂的区间划分问题,简化为简单的差分判断。
- 贪心依然成立,因为每次上涨都是独立可捕获的机会。
✅ 时间复杂度:O(n)
✅ 空间复杂度:O(1)
🌰 代码实现:
这段代码的精妙之处在于:它完全忽略了交易的具体时机,只关注价格变化的方向。就像一个永远乐观的投资者,只要市场有上涨,就抓住每一滴利润。
三、LeetCode 123:最多两次交易 —— 动态规划的经典教学案例
📌 题目重述:
最多允许进行 两笔交易,求最大利润。
示例:
[3,3,5,0,0,3,1,4]→ 最优解是两次交易:0→3 和 1→4,总利润6
❌ 为什么贪心失效?
你可能会想:"先做一次最优交易,再在剩余区间做第二次?"
错!因为第一次交易的选择会影响第二次的操作空间。
比如:
- 如果你在
[0,3]卖出太早,可能错过后面的低价[1]; - 或者第一次没卖,导致第二次资金不足……
这说明:决策之间存在依赖关系,不能再用贪心"分而治之"。
我们必须考虑全局状态转移。
✅ 正确解法:动态规划建模四个状态
我们将整个交易过程拆解为四个阶段:
| 状态 | 含义 | 初始值 |
|---|---|---|
buy1 | 第一次买入后的最大利润(花钱,所以是负数) | -prices[0] |
sell1 | 第一次卖出后的最大利润 | 0 |
buy2 | 第二次买入后的最大利润 | -prices[0] |
sell2 | 第二次卖出后的最大利润(最终答案) | 0 |
注意:这里的"利润"是相对初始资金而言的。例如 buy1 = -3 表示花了3块钱买了股票,当前净收益是-3。
🔁 状态转移逻辑(逐日更新):
对每一天的价格 price[i],我们按顺序更新四个状态:
💡 为什么这个顺序是对的?
关键在于:我们假设每天都可以"反悔"之前的决策。
比如:
- 昨天
buy1 = -5,今天股价跌到3 → 我们可以把buy1更新为-3,相当于"撤回昨天的买入,改成今天更低点买入"。 - 同理,
sell1可以后续更新为更高点卖出。
这种"允许反悔"的机制,正是 DP 强大的地方:它不是模拟真实交易流程,而是维护"截至目前所有可能路径中的最优状态"。
🧠 深层理解:状态机视角
我们可以把这个过程看作一个有限状态机:
start → buy1 → sell1 → buy2 → sell2
每一天,我们都尝试从上一个状态"推进"到下一个状态,或者保持原地。DP 的作用就是在这条链路上选出最优路径。
🌰 以实例 [3,3,5,0,0,3,1,4] 为例:
让我们一步步追踪状态变化:
| 天数 | 价格 | buy1 | sell1 | buy2 | sell2 |
|---|---|---|---|---|---|
| 0 | 3 | -3 | 0 | -3 | 0 |
| 1 | 3 | -3 | 0 | -3 | 0 |
| 2 | 5 | -3 | 2 | -3 | 0 |
| 3 | 0 | 0 | 2 | 2 | 2 |
| 4 | 0 | 0 | 2 | 2 | 2 |
| 5 | 3 | 0 | 2 | 2 | 5 |
| 6 | 1 | 0 | 2 | 1 | 5 |
| 7 | 4 | 0 | 2 | 1 | 6 |
可以看到,最终 sell2 = 6,即最大利润为6,与题目描述一致。
✅ 推广性极强!
这套模式可以轻松扩展到 k 次交易:
这段代码的精妙之处在于:它用四个变量构建了一个"状态机",每个变量都代表了交易过程中的一个关键节点。通过每天更新这些状态,我们实际上是在模拟所有可能的交易路径,并始终保留最优解。
🧩 总结对比:三种策略的本质区别
| 问题 | 交易次数 | 核心思想 | 算法范式 | 是否依赖历史状态 |
|---|---|---|---|---|
| 121 | 1次 | 抓住最低买入 + 最高卖出 | 贪心 | 只需维护最低价 |
| 122 | 无限次 | 所有上涨都要赚 | 贪心 | 每步独立 |
| 123 | 最多2次 | 平衡前后两次交易 | 动态规划 | 必须记录中间状态 |
🎯 核心结论:
- 贪心适用于"局部最优可累积成全局最优"的场景,如单次交易、每次上涨都赚钱。
- 动态规划适用于"多阶段决策且状态依赖"的问题,尤其是有限次数的资源分配类问题。
🌟 写在最后:算法即人生
这三道题不只是编程练习,更像是人生的隐喻:
- 新手期:追求"一击必杀",幻想抄到底部逃到顶部 → 结果往往踏空或套牢。
- 成长期:学会"见好就收",抓住每一个微小机会 → 积累复利。
- 成熟期:懂得"布局未来",权衡短期利益与长期规划 → 实现系统性胜利。
而算法的魅力就在于:它用最严谨的逻辑,告诉我们——
在不确定的世界里,如何做出确定的选择。
🧠 个人思考:
在刷题的过程中,我逐渐意识到:算法题的本质不是"解题",而是"建模"。我们需要把现实问题抽象成一个数学模型,然后找到最适合的算法范式。
股票交易题教会我的最重要一课是:不要被表象迷惑。当你看到"股票"、"交易"这些词时,很容易陷入金融思维,想着如何预测价格走势。但算法思维告诉我们:不需要预测未来,只需要对已知信息做出最优响应。
这就像人生中的很多决策:我们无法预知未来,但可以基于当前信息做出最佳选择。贪心算法教会我们抓住当下,动态规划教会我们规划未来。