LeetCode 123:买卖股票最佳时机III的状态机DP详解

发布时间:2026/10/17 10:21:21
LeetCode 123:买卖股票最佳时机III的状态机DP详解
前阵子在一个技术社群里看到有人问这道题评论区里清一色都是“这题看题解都看不懂”“状态定义我背下来了但换个题就不会了”。我仔细翻了一下发现 LeetCode 123. 买卖股票的最佳时机 III 这道题几乎是股票系列里从“入门”跨向“进阶”的一道分水岭也是动态规划状态机思想最经典的载体。题目本身不难读——你只有两次交易机会但恰恰是“最多两次”这个限制让习惯了一维 DP 的初学者第一次接触多状态、多阶段的转移。我用 Python 重新梳理这道题的完整思路和多种实现方式从状态机的直觉讲起到三维 DP、滚动数组优化再到边界条件里最容易被忽略的“负无穷”初始值每一处都会说清楚为什么这么设计。不管你是准备面试时刷题还是想彻底弄懂股票交易类动态规划的套路这篇文章应该都能帮上忙。1. 题目到底在问什么——先把手上的需求盘清楚1.1 从“最多两次”到灵光一现题目描述很朴素给定一个数组 pricesprices[i] 是某支股票第 i 天的价格。你只能选择在某一天买入之后的某一天卖出整个过程最多完成两笔交易而且必须先买后卖、手里最多持有一股求能获得的最大利润。注意几个细节“最多”两笔不是“恰好”两笔。你可以只做一笔甚至一笔都不做利润为 0。不能同一天买入又卖出严格来说当天买卖没有意义因为利润为 0不影响最优解但不允许把“买一次卖一次”拆成两笔。第二笔买入必须在第一笔卖出之后也就是不能同时持有两只股票。很多人在这一步就开始纠结贪心行不行先找一次最大利润再在剩余区间找第二次最大利润加起来不行吗不行。举个反例就能看出来假设价格序列是 [3,3,5,0,0,3,1,4]。如果贪心先找全局最大差价第一次是 0 买 4 卖最后两天利润 4剩下区间利润最多也就是第二天 3 买第四天 3 卖等于 0总利润 4。但正确答案是 3 买 5 卖利润 2 0 买 4 卖利润 4 6。贪心的局部最优会破坏第二次交易的潜在空间这种前后依赖的问题天然就是动态规划的菜。1.2 状态机模型的基本概念动态规划的核心从来不是“背模板”而是把问题变成一组“状态”再定义“状态之间怎么转移”。股票问题最经典的建模方式是状态机每个时刻你手里要么没有股票要么持有一支股票。在没有股票的状态下你可以选择买入进入持有状态在持有状态下你可以选择卖出回到空仓状态。“最多两笔交易”再引入一个维度已经完成了几笔交易或者说正在第几笔交易中。这样一来某个时刻你的处境可以描述成四个典型状态一次都没买过手里空仓。买过第一笔还没卖第一笔持有中。卖出了第一笔手里空仓且只能再买第二笔。买入了第二笔还没卖第二笔持有中。注意第 4 个状态之后还有一个隐藏的终态卖出第二笔手里空仓游戏结束。把这些状态串起来空仓未开始→ 第一笔持有 → 空仓第一笔已卖→ 第二笔持有 → 空仓全部结束。你每天只能做三件事什么都不做、买入只能从空仓到持有、卖出只能从持有到空仓而每一天的决策会影响你手里有多少现金或者成本。提示状态机模型的本质就是把决策的“历史”浓缩成几个有限的角色。你不用记住你买了多少钱、卖了多少钱只需要知道当前处境的角色以及这个角色下累计的最大利润。这就是为什么 DP 数组往往只有两三个维度却能表示所有可能性。搞清楚这一点后面所有代码都只是“把这段直觉翻译成数组和循环”而已。2. 为什么一度走弯路——贪心与错误状态设计的复盘2.1 误区一直接把交易次数当状态一种常见的错误写法是用 dp[i][j] 表示“前 i 天最多交易 j 次的最大利润”然后想当然地以为可以一层层推。这个思路本身没有错很多 DP 题确实是这么解的但问题在于股票的“买”和“卖”是两个动作单靠“交易次数”这一个维度无法区分你当前是持仓还是空仓。举个具体场景dp[i][1] 到底是指“前 i 天完成了一笔交易、现在是空仓”还是“前 i 天正处于第一笔交易中、手里还拿着股票”如果不区分转移方程根本写不出来——因为你不知道下一次买入是否合法。很多初学者在这个坑里卡了很久反复调整方程还是报错最后才意识到需要同时记录“是否持有股票”。2.2 误区二忽略“第 0 天之前”的负无穷另一个极其隐蔽的坑是初始值问题。假设我们用变量表示“第一笔买入后的状态”那在第 0 天还没开盘之前你不可能持有任何股票。这个“不可能”在代码里不能用 0 表示因为 0 意味着“利润为 0、手里有一支价值 0 的股票”这在逻辑上是错误的。正确做法是用一个足够小的负数比如 -inf 或 -10**9来表示“这个状态当前不可达”。只有这样才能保证转移时不会从非法的状态里推出一个虚高的结果。我见过很多次代码逻辑全对就是初始值用了 0结果测试用例一多就翻车。比如价格为 [1,2,4]期望答案是 3一次交易买 1 卖 4但错误的初始值可能会导出 7 之类的离谱数字。这就是“负无穷”这个细节的价值。2.3 动态规划状态设计的核心Hold 与 Sold 的划分经过前面两个误区的打磨最终的状态设计会自然收敛到一套标准模型。我用财经类的比喻来说明把你每天的资金账户分成四个仓位状态每个状态用一个变量记录“当前这个状态下能拿到的最好累计收益利润最大”。hold1已经买入第一笔当前持有股票。注意在这个状态下账面上的钱是“负数”花了买入价但为了统一计算我们用买入后剩余现金的最大值表示即可能是负的。sold1第一笔已经卖出手里是现金这笔交易结束后落袋的利润。hold2已经买入第二笔当前持有股票。这个状态下剩余的现金 第一笔利润 - 第二笔买入价也有可能是负的但通常我们会取最大。sold2第二笔已经卖出最终落袋利润。这四个变量和交易次数的对应关系变量对应状态是否持仓关键含义hold1进行第 1 笔买入后是剩余现金可能为负sold1完成第 1 笔卖出后否第一笔累计利润hold2进行第 2 笔买入后是第一笔利润减去第二次买入价sold2完成第 2 笔卖出后否最终累计利润其中 hold 系列的初始值必须为负无穷sold 系列的初始值为 0因为一开始不交易利润就是 0。注意所有“当前持有股票”的状态初始值都不能是 0否则等于默认了你白拿了一支股票这会让后续卖出产生不存在的利润。这套四状态模型就是 LeetCode 123 的标准解法也是后续扩展到“买卖股票的最佳时机 IVK 笔交易”的基石。3. 标准动态规划解法与完整落地实现3.1 状态转移方程的手推过程我们不用数组直接用四个滚动变量因为每一天的状态只和前一天相关。把所有状态压缩成四个变量每遍历一天就根据今天的价格p来更新它们。更新顺序是这道题最容易出错的地方。先看完整的式子hold1 max(hold1, -p) sold1 max(sold1, hold1 p) hold2 max(hold2, sold1 - p) sold2 max(sold2, hold2 p)等等细心的读者会发现这个式子里的 hold1 和 sold1 到底用的是更新前的值还是更新后的值顺序会不会有影响直觉上应该用“当天还没操作时的旧值”来计算其他状态否则同一天内可能发生“买完又卖”这种不合规的操作。但实际上上面的顺序是对的而且不用临时变量原因值得仔细分析。先看第一步hold1 max(hold1, -p)。它表示“要么保持之前的 hold1今天不买要么在今天以 p 的价格买入第一笔”。如果选择在今天买入新状态是-p显然这个值是从旧状态推出来的。再看第二步sold1 max(sold1, hold1 p)。这里的hold1是更新后的 hold1。假如今天价格下跌hold1可能被更新成-p即今天买入那么hold1 p就等于 0相当于“今天买入今天卖出”利润 0。这种同一天的买卖在真正的交易中没意义但它不会让答案变大所以不影响正确性。换句话说就算允许了这种操作最佳答案也不会因此变高DP 会自动忽略它。第三步和第四步同理hold2 max(hold2, sold1 - p)中的sold1已经包含了今天的卖出操作hold2 p也包含了今天买入又卖出的可能性。整体来看同一天内多个动作连环发生可能会导致答案不变量但不会虚高到错误。不过这里有一个细节值得验证如果同一天买第一笔、卖第一笔、再买第二笔、再卖第二笔理论上会算出一个利润 0 的动作不影响最大值所以结果依然正确。这就是我们常说的“同一天操作在数学上允许但不会优于更优解”。为了更严谨也可以拆成两轮更新用旧值计算# 先记录旧值 old_hold1 hold1 old_sold1 sold1 old_hold2 hold2 hold1 max(old_hold1, -p) sold1 max(old_sold1, old_hold1 p) hold2 max(old_hold2, old_sold1 - p) sold2 max(sold2, old_hold2 p)但实际上用更新后的值即上面四个式子顺序执行在本题中只会多算“同一天买然后又卖”的无利润操作不会产生虚假增益。我在码代码时习惯用更新后的简洁写法因为性能一样代码更短。3.2 Python 实现从三维 DP 到滚动数组先给一个最容易理解的版本三维 DP。用dp[i][k][0]表示第 i 天结束后已经最多进行了 k 笔交易以卖出计数或买入计数不同题目定义略有差异手里不持有股票的最大利润dp[i][k][1]表示手里持有股票的最大利润。关于k的计数口径这里有一个常见歧义。标准写法把“买入”算作一笔新交易的开始这样k表示“已经完成了 k 次买入”。转移方程dp[i][k][0] max(dp[i-1][k][0], dp[i-1][k][1] prices[i]) dp[i][k][1] max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])当 k1 时dp[i][1][1] 只能从 dp[i-1][0][0] 转移而 dp[0][0][0] 0dp[0][0][1] -inf所以相当于从“空仓未开始”状态买入这就是第一笔交易。当 k2 时同理第二笔买入必须先从 k1 的空仓状态来。但很多题解把k当作“卖出次数”导致方程里dp[i][k][0]和dp[i][k][1]的 k 下标会错位非常容易混淆。建议初学时统一用 Google 官方风格k表示已经进行的交易次数每买入一次k 就加一这样最好记忆。代码实现def maxProfit(self, prices: List[int]) - int: if not prices: return 0 n len(prices) # dp[i][k][0/1] # k 最大为 2 dp [[[0] * 2 for _ in range(3)] for _ in range(n)] # 初始化第 0 天 dp[0][0][0] 0 dp[0][0][1] -10**9 # 不可能持有 dp[0][1][0] 0 dp[0][1][1] -prices[0] # 第 0 天买入第一笔 dp[0][2][0] 0 dp[0][2][1] -10**9 # 第 0 天不可能做第二笔买入因为还没卖出第一笔 for i in range(1, n): p prices[i] for k in range(3): # 不持有昨天也不持有或昨天持有今天卖出 dp[i][k][0] max(dp[i-1][k][0], dp[i-1][k][1] p) # 持有昨天也持有或昨天不持有今天买入 if k 0: dp[i][k][1] max(dp[i-1][k][1], dp[i-1][k-1][0] - p) else: dp[i][k][1] dp[i-1][k][1] # k0 时不可能买入因为买入就变成 k1 return max(dp[n-1][k][0] for k in range(3))这个版本的好处是结构清晰每个状态都有位置可查适合在纸上推演时对照。坏处是空间复杂度 O(n)在实际刷题中没必要保留所有天数因为递推只依赖前一天。优化一版去掉 i 的维度只保留 k 的二维状态def maxProfit(self, prices: List[int]) - int: if not prices: return 0 # dp[k][0]最多 k 次交易当前空仓的最大利润 # dp[k][1]最多 k 次交易当前持仓的最大利润 dp [[0] * 2 for _ in range(3)] dp[0][0] 0 dp[0][1] -10**9 dp[1][0] 0 dp[1][1] -10**9 dp[2][0] 0 dp[2][1] -10**9 for p in prices: for k in range(1, 3): dp[k][1] max(dp[k][1], dp[k-1][0] - p) dp[k][0] max(dp[k][0], dp[k][1] p) return max(dp[k][0] for k in range(3))注意这里循环内先更新dp[k][1]再更新dp[k][0]顺序和之前四个变量的版本略有不同先更新持仓状态买入再更新空仓状态卖出。这样dp[k][1] p用到的dp[k][1]是当天更新后的持仓值可能会算入“同一天买入又卖出”。按照前文的分析这不影响最终最大值因为同一天倒买倒卖的利润为 0。不过如果你想严格避免这种“同一天先买后卖”的中间状态可以把内层循环改成倒序更新 k或者直接用四个变量的写法。实际测试中这种写法都能通过全部测试用例因为题目中同一天买入卖出不会带来额外利润。3.3 内存优化版本6 个变量的写法最后给出最常见也最推荐记忆的版本——四个变量严格说是四个核心变量但加上临时理解可以认为只有四个def maxProfit(self, prices: List[int]) - int: if not prices: return 0 # 初始化第 0 天之前的状态 hold1 -prices[0] # 第 0 天买入第一笔 sold1 0 # 还没卖出利润 0 hold2 -prices[0] # 第 0 天不可能做第二笔买入但为了统一用 -prices[0] sold2 0 # 终态利润 0 # 注意hold2 的初始值其实应该为 -inf这里用 -prices[0] 是否安全 # 如果第 0 天买入第二笔利润是 -prices[0]这明显是不合理的 # 但等到第 1 天hold2 max(hold2, sold1 - p1) # 如果旧 hold2 -prices[0]sold1 - p1 -p1因为 sold10 # 取最大值时旧 hold2 可能不是全局最优但最终不会影响 sold2 的正确性 # 因为第二笔买入必然发生在第一笔卖出之后所以真正有用的 hold2 一定来自 sold1 - p。 # 但为了严谨建议还是将 hold2 初始化为 -10**9。 hold1 -10**9 sold1 0 hold2 -10**9 sold2 0 for p in prices: # 注意顺序先更新 hold1再 sold1再 hold2最后 sold2 hold1 max(hold1, -p) # 今天买入第一笔 or 继续持有 sold1 max(sold1, hold1 p) # 今天卖出第一笔 or 保持 hold2 max(hold2, sold1 - p) # 今天买入第二笔 or 继续持有 sold2 max(sold2, hold2 p) # 今天卖出第二笔 or 保持 return sold2这段代码里最需要留意的就是hold2的初始值。注释里详细讨论了用 -prices[0] 还是 -inf如果用-prices[0]相当于默认“第 0 天买入第二笔”是合法状态这会污染后续hold2的计算吗我们看实际递推第 1 天时hold2 max(旧hold2, sold1 - p1)如果旧 hold2 -prices[0] -3而sold1 - p1 0 - 5 -5那么 max(-3, -5) -3好像没问题。但假如 prices [3, 5, 1, 2]正确答案是 3 买 5 卖利润 2 1 买 2 卖利润 1 3。如果 hold2 初始化为 -prices[0] -3到第二天它可能是 -3, 第三天sold1已经变成 2sold1 - p2 2 - 1 1所以第 3 天hold2 max(-3, 1) 1最后还是能正确买入。问题在于初始时 hold2 -3 比 -inf 大会不会让某一天的sold2提前从错误的hold2中卖出“不存在的第二笔”理论上存在风险。比如 prices [3,5,1,2]假设第 0 天把 hold2 设为 -3第 1 天 sold2 max(0, -3 5) 2不对我们再仔细推第 0 天hold1-3, sold10, hold2-3, sold20。第 1 天 p5hold1max(-3,-5)-3sold1max(0,-35)2hold2max(-3,2-5-3)-3sold2max(0,-35)2。第 2 天 p1hold1max(-3,-1)-1因为-1-3买入价1更便宜sold1max(2,-110)2hold2max(-3,2-11)1sold2max(2,112)2。第 3 天 p2hold1max(-1,-2)-1sold1max(2,-121)2hold2max(1,2-20)1sold2max(2,123)3。最后 sold23结果没错。但如果把 hold2 初始值设为 -inf 呢第 1 天 hold2max(-inf, 2-5-3)-3第 2 天 hold2max(-3,1)1结果一样。所以看起来在多数情况下无伤大雅但为了逻辑严谨我推荐一律使用 -10**9或 -inf作为所有 hold 状态包括 hold1 和 hold2的初始值除非你明确知道自己在做什么。注意hold1的初始值也建议用 -10**9 而不是 -prices[0]因为如果 prices[0] 是 0 或负数现实中股价不会为负但某些测试数据可能包含 0用 -0 会让“不持有但可以买入”的状态混淆。统一用 -inf最安全。4. 边界条件与理解关键点再剖析4.1 为什么要用负无穷而不是 0用一个表格直观对比初始值方案hold1sold1hold2sold2风险全部 00000可能在未买入时“卖出”产生虚假利润买入用 -prices[0]-p00-p00第二笔买入初始值过“高”有轻微污染风险买入用 -inf-inf0-inf0符合逻辑不可达状态保持绝对不可达为什么“负无穷”这么重要因为 DP 的转移是取 max。如果不可达状态是 0那么任何“从不可达状态卖出”都会得到 0 p p这会凭空多出一笔利润。比如假设你还没有买入第一笔就直接从 hold1 卖出sell1 变成今天的股价显然错误。使用 -inf 后未买入状态的持有利润是负无穷卖出后依然是负无穷附近的值会被 max 自动淘汰。这里给一个实际反例prices [1, 2, 3, 4, 5]。正确答案是一次交易利润 41 买 5 卖或分两次利润也是 41买2卖 3买4卖都是 4实际上是 112最大是 4但如果你把 hold1 初始化为 0第一天 hold1 max(0, -1) 0第二天 sold1 max(0, 0 2) 2凭空出现了 2 的利润整条链的错误就蔓延开了。4.2 状态机遍历顺序为什么是按天数而不是按交易次数很多人在背模板时会纠结一个问题为什么外层循环是“每一天”内层才是“交易次数”如果交换内外层可不可以在三维 DP 版本中状态转移只依赖前一天的相同 k 的旧值所以按照天数从前往后遍历是自然的。如果先遍历 k 再遍历天由于 k 增加时依赖dp[i-1][k-1][0]你依然需要记住所有天数上、上一档 k 的数据内存反而更大而且滚动数组不方便维护。所以实际编码都采用“天”作为外层循环。更深层的原因是股票买卖题目中“次数”维度天生就小于“天数”维度交易的发生以天为单位。按天推进状态机每一天的决策天然覆盖所有 k代码结构最紧凑。4.3 案例手推逐步走一遍 prices [3,3,5,0,0,3,1,4]用四变量法手动走一遍对理解极有帮助。设初始hold1 -inf, sold1 0, hold2 -inf, sold2 0第 1 天 p3hold1 max(-inf, -3) -3sold1 max(0, -330) 0hold2 max(-inf, 0-3-3) -3sold2 max(0, -330) 0第 2 天 p3hold1 max(-3, -3) -3sold1 max(0, -330) 0hold2 max(-3, 0-3-3) -3sold2 max(0, -330) 0第 3 天 p5hold1 max(-3, -5) -3选择在第 1 天买入今天的 5 太贵不买sold1 max(0, -352) 2hold2 max(-3, 0-5-5) -3还是用第一天的成本当第二笔不对第二笔应依赖 sold1 - p 2-5 -3旧 hold2 也是 -3所以不变sold2 max(0, -352) 2第 4 天 p0hold1 max(-3, -0) -3sold1 max(2, -30-3) 2hold2 max(-3, 2-02) 2sold2 max(2, 202) 2注意这里 hold2 从 -3 跳到了 2含义是第一笔赚了 2然后以 0 的价格买入第二笔所以剩余现金 2 - 0 2。这个状态表示“已经锁定了第一笔利润并持有第二支股票”。第 5 天 p0hold1 max(-3, 0) 0这里注意今天 0 元买入持有成本为 0比之前的 -3 更大所以更新成 0。这意味着最佳策略变成“以 0 买入持有第一笔”虽然现实中不太可能但算法认可sold1 max(2, 000) 2hold2 max(2, 2-02) 2sold2 max(2, 202) 2第 6 天 p3hold1 max(0, -3) 0sold1 max(2, 033) 3hold2 max(2, 2-3-1) 2sold2 max(2, 235) 5第 7 天 p1hold1 max(0, -1) 0sold1 max(3, 011) 3hold2 max(2, 3-12) 2sold2 max(5, 213) 5第 8 天 p4hold1 max(0, -4) 0sold1 max(3, 044) 4hold2 max(2, 4-40) 2sold2 max(5, 246) 6最终返回 6。和前面贪心例子里算出的 6 吻合。手动推一遍的最大价值是让你看到hold2不是从hold1转移来的而是从sold1转移来的。很多之前搞混关系的人推完这个例子基本就通了。5. 常见问题与排查技巧实录5.1 常见错误类型与排查表根据我刷题和看别人代码的经验LeetCode 123 常见的错误高度集中在几个点上。直接整理成速查表症状原因修复方法输出比正确答案大初始值用了 0 而不是 -inf导致凭空卖出所有 hold 初始化为 -10**9输出为 0 但预期大于 0循环顺序写错导致买入永远发生在卖出之后检查更新顺序hold → sold结果依赖 prices[0] 符号hold1 初始化用了 -prices[0]当价格为 0 时出现歧义统一用 -10**9交易次数限制没生效内层循环从 1 到 k 时没有保证“最多两次”检查 k 维度的上限是否可以出现第 3 笔相邻案例能过复杂的乱状态转移时使用了当天更新后的值产生连环操作重构为四个变量或按旧值保存后再更新返回 sold1 或 sold2 不对最终结果应该取所有空仓状态的最大值用 max(sold1, sold2, 0) 或直接返回 sold2因为 sold2 一定 sold1其中“输出比正确答案大”是最常见的几乎每个放弃这道题的人都栽在这里过。5.2 面试时最容易被追问的两个问题面试官常会问“如果题目改成最多 K 次交易怎么办”这就是 LeetCode 188 买卖股票的最佳时机 IV。解法就是把两个固定变量换成数组外层循环天数内层循环次数转移方程不变只是k变成动态上限。这个扩展几乎可以直接套用上一节的二维数组版本。第二个高频追问是“为什么要四个状态而不是两个”回答思路是两个状态持有/空仓只能表示一次交易因为第二次买入时依赖“第一笔已经卖出的利润”而持有空仓状态无法区分你到底是第几次的持有、第几次的空仓。引入 hold1/sold1/hold2/sold2 是为了把“交易进行到第几笔”这个信息编码进状态。5.3 一个容易被忽略的直觉提速所有交易次数大于天数半数的题目顺着上面的思路我还想分享一个偷懒技巧如果你在解决 K 次交易问题时发现 k n // 2那么其实可以当作无限次交易来处理直接用贪心累加所有上涨段的差价。原因是每笔交易至少需要两天最多能完成 n // 2 笔交易如果 K 超过了这个上限限制就形同虚设。这个技巧在 LeetCode 188 里非常实用能让代码少算很多 DP。6. 题目变体与扩展思考6.1 从一次、两次到 K 次股票系列的整体脉络股票类问题的大家族里至少有六道经典题。把它们放在一起看状态机的思想完全统一题目编号描述核心限制解法思路121最佳时机 I只能一次交易维护历史最低价贪心扫描122最佳时机 II不限次数累加所有上涨段123最佳时机 III最多两次四状态 DP 或三维 DP188最佳时机 IV最多 K 次二维滚动 DPK 超大时贪心309含冷冻期卖完后隔一天才能买状态机多一个“冷冻”状态714含手续费每次交易有手续费买入时成本加上手续费即可掌握了 123 题的状态机思想其他题目基本是“加状态”或“改方程”的区别。6.2 交易次数与冷却期的叠加如果你把 123 的“最多两次”和 309 的“冷冻期”叠加起来会得到更复杂的题目。思路依然不变无非是在状态机中加入“冷冻中”这个角色。只要心里始终用“角色 累计利润”来描述每个时刻就不会乱。6.3 为什么动态规划适合这类问题最后说点务虚的。股票问题看似是“决策问题”每天买或卖但一旦加上交易次数限制就变成了“最优化组合”问题。贪婪不行回溯太慢只有动态规划能同时处理“时间顺序”和“次数限制”这两个约束。状态机把“次数限制”离散成几个状态角色DP 再每天根据选择更新角色的最佳收益一套组合拳完美契合问题的结构。我在实际刷题中还有一个小习惯把 123 题的四个变量打印出来跑几个样例观察每个变量的变化轨迹。基本上跑通两个用例你对“sold1 是 hold2 的起点”这件事的印象就再也丢不掉了。这个方法比看十篇题解都管用建议你也可以试试。至于初始化为什么非要写成 -109 而不是 -float(inf)这个其实取决于你对 Python 的熟悉程度。用 -float(inf) 的好处是可读性强坏处是在某些运算中 inf 参与 max 不会出错但在个别语言里整型和浮点混用会带来类型问题。LeetCode 的 Python 环境里两种写法都能过我建议用 -109因为题目给的价格上限是 10^4天数上限 10^5理论最大利润不超过 10^9所以 -10**9 足够小、足够安全而且全程整型运算更快。这话不完全严谨但胜在实用。总结一下我踩过的坑第一次写这题时我把卖出和买入的顺序写反了导致 sold1 永远先于 hold1 更新结果是每天都卖出“不存在的股票”越算越大。调了半天才意识到DP 必须先有“买入”这个动作才轮到“卖出”来取最大值。从那以后我养成了一个习惯凡是涉及买入卖出的状态机永远按“先买后卖”的顺序写更新语句宁可多写几行也要让逻辑顺序严格对应现实交易顺序。这个经验希望对你能有帮助。