动态规划 31. 股票问题总结(类别解析)

动态规划 31. 股票问题总结(类别解析)

股票问题给我做的有一些混乱,因此本总结主要是借助GPT的帮助帮我解决下面的核心问题,也希望能通过这些示例与讲解,帮助各位快速厘清各种“股票问题”的通用 DP 思路

经典股票问题:

  1. 动态规划 25. 买卖股票的最佳时机-CSDN博客
  2. 动态规划 26. 买卖股票的最佳时机II-CSDN博客
  3. 动态规划 27. 买卖股票的最佳时机III(多状态转换初遇)-CSDN博客
  4. 动态规划 28. 买卖股票的最佳时机IV(多状态转换扩展——k状态)-CSDN博客
  5. 动态规划 29. 最佳买卖股票时机含冷冻期(股票二变式)-CSDN博客
  6. 动态规划 30. 买卖股票的最佳时机含手续费(股票二变式)-CSDN博客

问题一

不同股票问题之间的区别是什么?

尤其是为什么股票+冷冻期和股票二是变式,但是股票三四和股票二又不是同类了?

股票二理论上应该也能找到一个最大交易次数,

为什么股票二就只需要开两个状态,股票+冷冻期就只需要开三个或者四个状态,而股票三四就得开很多状态?

下面的答案中的dp数组设置仅供参考,作为理解用即可,实际上代码中的dp数组不一定使用这样的设置!(尤其是股票三、四)

0. 常见的几道“股票交易”题

以下是 LeetCode 上最常见、也最具代表性的几道股票交易题(中文名只是方便理解,不是官方名称):

  1. 买卖股票的最佳时机 I(LC 121)

    • 最多只允许交易 1 次(只买一次,卖一次)
    • 通常只需要一重循环或简单记录“历史最低价”,就能求最大利润。
  2. 买卖股票的最佳时机 II(LC 122)

    • 可以进行多次交易,不限次数,但不允许“同一天既买又卖”。
    • 解法常见的有贪心(把所有“上坡”差价加起来)或简单的 2D DP:dp[i][0 or 1]​。
  3. 买卖股票的最佳时机 III(LC 123)

    • 最多允许 2 次交易
    • 需要用一个含“交易次数”维度的 DP,或者分阶段思考(第一次交易、第二次交易)。
  4. 买卖股票的最佳时机 IV(LC 188)

    • 最多允许 k 次交易
    • 一般要在 DP 里加一维表示“已经完成了多少次交易”或“还剩余多少次交易可用”。
  5. 买卖股票含冷冻期(LC 309)

    • 无限次交易,但是“卖出后,下个交易日不能马上买入”,需要至少冷冻 1 天。
    • 可以用“持股 / 刚卖出(冷冻) / 不持股非冷冻”三种状态,或类似思路。
  6. 买卖股票含手续费(LC 714)

    • 无限次交易,但每次卖出都需要支付一定交易费(或可看做买入/卖出时扣费),不限制冷冻期,也不限制交易次数。
    • dp​ 的转移时,把手续费体现在买入或卖出操作上即可。

从上面可以看出,每道题变来变去的要点就是交易次数是否有限是否有冷冻期是否有手续费等等。由于限制不同,就导致了状态或转移方程不同。


1. 为什么说「股票 + 冷冻期」跟「股票 II」相似?

  • 股票 II:无限次交易,可以随时买卖(只要买在卖之前)。

  • 股票 + 冷冻期:本质上也是可以进行无限次交易,只是额外加了一条限制:当你某天卖出之后,第二天不能马上买,要空过一天。所以它可以理解为“股票 II + 1 天冷冻期”。

  • 因为交易次数不限制,所以我们不需要额外记录“还剩多少次交易可做”,不需要在 DP 里出现“第几次交易”的维度。

  • 只需要在状态或转移中处理好 “卖出后下一天不能立即买” 的限制即可。于是出现了常见的 3 种(或 2+1 种)状态写法:

    • dp[i][0]​: 持股
    • dp[i][1]​: 不持股,且今天正好卖出(意味着今天算冷冻日)
    • dp[i][2]​: 不持股,且今天不处于冷冻期

这就是为什么「股票 + 冷冻期」一般只用到三种或四种状态,而不需要再开一维记录交易次数。


2. 为什么「股票 III、IV」需要记录交易次数?

  • 股票 III(最多 2 次交易)、股票 IV(最多 k 次交易)与股票 II的主要差异是:交易次数是“有限制”的。

  • 如果只需最多 1 次交易(LC 121),我们只要在一重循环里维护“最低价”和“当前利润”就行。

  • 当允许最多 2 次或 3 次或 k 次……如果还想用 DP 去做,就需要在状态里增加一维,表示当前已经完成了多少笔交易或还能做多少笔交易。

  • 比如股票 III,有几种常见写法之一是:

    dp[i][j][0 or 1]
    
    • i​ 表示第 i 天
    • j​ 表示已经完成的交易次数(0~2)
    • 第三维 0/1 表示不持股/持股
  • 这样每个 dp[i][j][0]​ 和 dp[i][j][1]​ 就能知道:到了第 i 天,在用了 j 次交易之后,我不持股/持股的最大利润是多少。

  • 当 j 达到 2(对于“最多两次交易”),或者 j 达到 k(对于“最多 k 次交易”),就不能继续开新仓交易了,只能等待卖出或维持不变(具体看题意)。

为什么要多一维? 因为在每一次买入卖出的过程中,你需要知道“我还剩多少次买卖的配额”或“我当前是第一次交易还是第二次交易?”,否则就没法控制“最多只能做 2 笔、3 笔或 k 笔”。


3. 为什么「股票 II」也能做无数次交易,却不用开交易次数维度?

  • 对于「股票 II」,交易次数不限制

    • 不用关心“还剩多少次能交易”——想买就买,想卖就卖。
    • 所以多开一维“交易次数”也可以,但是那维度会是无限大,不现实,也没必要!
  • 因而「股票 II」只需要记录“当前是否持股”,即:

    • dp[i][0]​: 第 i 天结束后,不持股状态的最大利润
    • dp[i][1]​: 第 i 天结束后,持股状态的最大利润
  • 这样写就够了,而且转移很简单:

    dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])  # 今天不操作 or 今天卖出
    dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])  # 今天不操作 or 今天买入
    
  • “股票 + 冷冻期”其实就是「股票 II」在这个基础上再挖一个小坑:卖出后的隔天不能买,所以加一个额外的状态或额外逻辑去处理即可。


4. 为什么「股票 III、IV」不能直接用贪心或 2D DP 不带次数?

  • 因为对「III、IV」而言,交易次数是有限的:

    • 你不能想买就买、想卖就卖,否则有可能会超出交易次数上限。
    • 所以必须在 DP 中跟踪“已经用了几次交易”。
  • 当交易次数受限时,“贪心”就不一定能随便用了。比如“碰到波谷就买,碰到波峰就卖”这种做法,如果你的交易次数只有 2 次,但是价格波动很多,哪一次该抓、哪一次该放,需要更多全局权衡。


5. 小结:不同题为什么状态维数不同?

  1. 无限次 vs 有限次:是否需要引入“剩余可用交易次数”或“已完成次数”这一维。

  2. 是否有冷冻期 / 手续费 / 交易税等特殊限制

    • 若有,需要在状态或转移中“多做一些区分”,比如“刚卖完是不是在冷冻期”、“卖出时扣手续费”、“卖完要跳过 k 天”等等。
    • 这些限制可能会带来“多 1~2 个状态”或在转移时多一些特殊逻辑。

一些常见题的状态设计对比表,可以是这样(只是示意,具体实现可能略有差别):

题目 交易次数限制 是否有其他特殊限制 状态设计举例
LC 121(股票 I) 最多 1 次 不需要 DP,或dp[i] = maxProfit​(仅存最低价)
LC 122(股票 II) 无限次 dp[i][0 or 1]
LC 309(冷冻期) 无限次 卖出后冷冻 1 天 dp[i][0 or 1 or 2]​或类似
LC 123(股票 III) 最多 2 次 dp[i][j][0 or 1]​, j ∈ {0,1,2}
LC 188(股票 IV) 最多 k 次 dp[i][j][0 or 1]​, j ∈ {0..k}
LC 714(含手续费) 无限次 每笔卖出收手续费 dp[i][0 or 1]​,在卖出操作或买入操作里扣费

简单来说:

  • “无限次” → 不关心“我做了几次交易了”,只需管“我现在有没有持股”,外加冷冻期/手续费/别的限制就行。
  • “最多 k 次” → 需显式记录“我现在是第几次交易”或“还剩几次交易”。

6. 回答你提出的疑惑

「为什么股票 + 冷冻期(309)和股票 II(122)是同类,但是股票 III、IV 和股票 II 就又不是了?」

  • 股票 + 冷冻期 其实就是 股票 II(无限次交易) 再加一个“卖出后下一天不能买入”的限制;两者都不限制交易次数,所以思路/状态比较相似,只不过 309 题里要额外处理“冷冻期”。

  • 股票 III、IV 则是“有交易次数上限”的情形,它们主要用在“只允许最多 2 次 / k 次”买卖,如果你不记“多少次交易已经用完”,就没法限制你不能再交易了,必须在 DP 中多开一个“次数”维度。

    • 这样就和“无限次交易”不是同一种思路了。

「股票 II 理论上是不是也能算有最大交易次数?」

  • 可以说股票 II 就是“最多能进行无限次交易”,即所谓 k = +∞​ 的情况。如果 k​ 无限大,那就不必再记录 k​ 了,因为你永远不会用完。
  • 从实现角度看,如果真的要写成“最多 k 次”的形式,那 k​ 会很大,就没必要;直接用一对 dp[i][0 or 1]​ 就行了,更简洁。

「为什么‘股票二只需要开两个状态’,而‘股票+冷冻期’只需要开三个/四个状态,‘股票三四’就得开很多状态?」

  • 股票 II(无限次、无手续费、无冷冻期)只需记录:

    • dp[i][0]​: 第 i 天不持股
    • dp[i][1]​: 第 i 天持股
      两个状态就够。
  • 股票 + 冷冻期(309)也是无限次,但**多了“冷冻期”**这一个限制,为了在“转移”时区分“今天能不能买”,就会多 1~2 个状态(看你怎么拆分)。

    • 常见写法是 3 个状态:持股 / 今天刚卖(冷冻) / 不持股且无冷冻。
  • 股票 III、IV 是“最多 k 次”交易,它跟“冷冻期”没关系,重点是必须记录还剩多少次交易才能控制不超过限额。所以要多开一个“交易次数”维度,如 dp[i][j][0 or 1]​,这样状态量就多了。

换言之:

  • 有没有交易次数限制” 决定了需不需要多开一个“交易次数”维度。
  • 有没有冷冻期、手续费” 决定了在“是否持股”之外需不需要再增加“冷冻 / fee 处理”等额外状态或复杂转移。

7. 总结

  1. 先分清交易次数是否有限(若有限,就必须在 DP 里出现“第几次交易”之类的维度;若无限,则只要关心“当前是否持股”就够了)。
  2. 再分清是否有额外限制(冷冻期、手续费等等),有的话要在状态或转移中额外加入一些区分和计算。

因此,你会看到:

  • 股票 II股票 + 冷冻期 其实都属于“无限次”类别的,只不过后者多了一个“冷冻”的限制,所以状态数从 2 个涨到了 3/4 个。
  • 股票 III、IV 则是“有限次”类别,为了记录“用掉了几次交易”,就需要多一维 DP,所以“状态”看起来比单纯的「无限次 + 冷冻期」更加复杂。

这就是为什么有时题目只需开 2 个或 3 个状态,而有时题目需要开很多状态/多一维度的根本原因。希望能帮你理清思路。

问题二:

给出常见的各类股票问题代码模板,并比较说明增加了什么或者减少了什么。

下面的模板不一定完全正确!仅供我自己参考用


下面给你一个“常见股票问题”的代码模板+思路比较总览,涵盖以下几类:

  1. 买卖股票的最佳时机 I(LC 121;最多一次交易)
  2. 买卖股票的最佳时机 II(LC 122;无限次交易)
  3. 买卖股票的最佳时机 III(LC 123;最多 2 次交易)
  4. 买卖股票的最佳时机 IV(LC 188;最多 k 次交易)
  5. 买卖股票含冷冻期(LC 309;无限次交易+冷冻期)
  6. 买卖股票含手续费(LC 714;无限次交易+手续费)

下面的示例均以 Python 为例,演示自底向上的 DP 模板(部分题也有更简洁的写法,这里主要展示统一的“DP 思路”对比)。在每个模板后,会比较说明它比前面哪些题“多或少了什么限制”以及“在状态或转移中做了哪些变化”。


1. 买卖股票的最佳时机 I(LC 121)

限制:

  • 最多只允许交易 1 次(即只买 1 次、卖 1 次)。

常见最简写法:

  • 只需要记录一个“历史最低价”和对应的最大利润即可。
  • 不太需要 DP 了,时间复杂度 O(n)、空间 O(1)。
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        _min = float('inf') # 从左向右遇到的目前的最小值
        ans = 0 # 目前所能获取的最大利润
        for i in range(len(prices)):
            _min = min(_min, prices[i]) # 更新最小值
            ans = max(ans, prices[i] - _min) # 更新最大利润
        
        return ans

对比说明

  • 这里没有“持股 / 不持股”多维状态,因为只进行 1 次交易,直接用“最低买入价”计算最大差值即可。
  • 与后面那些支持多次交易的题相比,这个最简单。

附一维dp:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # dp0:表示当天 **持有股票** 时的最大收益(最优方案)。
        # dp1:表示当天 **不持有股票** 时的最大收益(最优方案)。
        # 初始化第0天的,以开始递推
        dp0, dp1 = -prices[0], 0

        for i in range(1, len(prices)): # 第i天,要从索引1天开始
            old_dp0 = dp0
            # 今天持有:要么昨天已经在持有,要么之前从来没买过(手上现金额没使用过,还是0)今天刚买入
            dp0 = max(old_dp0, -prices[i])
            # 今天不持有:要么昨天也不持有,要么昨天持有今天刚卖出
            dp1 = max(dp1, old_dp0 + prices[i])

        return max(dp0, dp1)

2. 买卖股票的最佳时机 II(LC 122)

限制:

  • 可以进行多次交易,无限次买卖;
  • 同一天内不允许多次操作(必须先卖掉才能再买)。

DP 思路(自底向上,二维):

  • dp[i][0]​: 第 i​ 天收盘后 不持股 时的最大收益;
  • dp[i][1]​: 第 i​ 天收盘后 持股 时的最大收益。

状态转移

dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) 
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]) 

代码模板:

def maxProfit_2(prices):
    n = len(prices)
    dp = [[0, 0] for _ in range(n)]
    
    # 初始化
    dp[0][0] = 0          # 第0天不持股
    dp[0][1] = -prices[0] # 第0天持股(买入)
    
    for i in range(1, n):
        dp[i][0] = max(
            dp[i-1][0],            # 继续不持股
            dp[i-1][1] + prices[i] # 今天卖出
        )
        dp[i][1] = max(
            dp[i-1][1],            # 继续持股
            dp[i-1][0] - prices[i] # 今天买入
        )
    
    return dp[n-1][0]  # 最后一天不持股时利润最大

对比说明

  • 与“股票 I”相比,增加了“可以多次交易”,因此需要在 DP 中区分持股/不持股的状态
  • 没有交易次数限制,所以不需要额外的“交易次数”维度

3. 买卖股票的最佳时机 III(LC 123)

限制:

  • 最多 2 次交易
  • 不能同时参与多笔交易(买之前必须先卖掉手里的票)。

DP 思路(自底向上,三维):

  • 常见的一种写法:dp[i][j][0 or 1]

    • i​: 第 i​ 天 (0-based)
    • j​: 已完成的交易次数 (0 ~ 2)
    • 第三维 0 / 1:表示当日收盘后不持股 / 持股
  • 注意上面这种写法可能有问题!只看分析就好,代码还是看后面股票四附上的题解!

状态转移

  • 不持股 (dp[i][j][0]​):要么前一天也不持股,要么前一天持股并在今天卖掉
  • 持股 (dp[i][j][1]​):要么前一天就持股,要么前一天不持股并在今天买入

对比说明

  • 与“股票 II”相比,这里新增了一维 j表示已经用了多少次交易,从而保证最多只能进行 2 次交易。
  • 没有“冷冻期”或“手续费”额外限制,因此只要“持股/不持股 + 交易次数”就够了。

4. 买卖股票的最佳时机 IV(LC 188)

限制:

  • 最多 k 次交易(k 可大可小)。
  • 同样不能同时进行多笔交易。

DP 思路

  • 类似于“股票 III”,只是 j​ 的上限从 2 换成了 k​。
  • 代码基本相同,只是“for j in range(k+1)”而已。

对比说明

  • 相比“股票 III”,唯一的变化就是把交易次数上限写成 k,在循环里 for j in range(k+1)​.
  • k​ 非常大时,可以用“股票 II”思路特判一下(因为相当于无限次交易)。

​**灵茶山艾府的题解**​ :(直接涵盖了股票三)

1:1 翻译成递推

  • 问:为什么第二维度的大小是 k+2?

    答:在记忆化搜索中,j 的范围是 [−1,k],这一共有 k+2 个数。1:1 翻译成递推就需要 k+2 的数组大小。

    问:f 数组中的 j=0 表示什么意思?

    答:这对应着记忆化搜索中的 j=−1 的状态,也就是交易 −1 次的状态。注意这是不合法的,所以初始值一定是 −∞。

    问:f 的初始值怎么确定?

    答:f 的初始值来自记忆化搜索的递归边界,递归边界怎么写,初始值就怎么写。

    class Solution:
        def maxProfit(self, k: int, prices: List[int]) -> int:
            n = len(prices)
            f = [[[-inf] * 2 for _ in range(k + 2)] for _ in range(n + 1)]
            for j in range(1, k + 2):
                f[0][j][0] = 0
            for i, p in enumerate(prices):
                for j in range(1, k + 2):
                    f[i + 1][j][0] = max(f[i][j][0], f[i][j][1] + p)
                    f[i + 1][j][1] = max(f[i][j][1], f[i][j - 1][0] - p)
            return f[-1][-1][0]
    

    复杂度分析

    时间复杂度:O(nk),其中 n 为 prices 的长度。

    空间复杂度:O(nk)。

空间优化

  • class Solution:
        def maxProfit(self, k: int, prices: List[int]) -> int:
            f = [[-inf] * 2 for _ in range(k + 2)]
            for j in range(1, k + 2):
                f[j][0] = 0
            for p in prices:
                for j in range(k + 1, 0, -1): # 注意倒序
                    f[j][0] = max(f[j][0], f[j][1] + p)
                    f[j][1] = max(f[j][1], f[j - 1][0] - p)
            return f[-1][0]
    

    复杂度分析

    时间复杂度:O(nk),其中 n 为 prices 的长度。

    空间复杂度:O(k)。

上面的设置和思路其实并没有那么好理解,故也可以按照代码随想录的方法来:

class Solution:
    def maxProfit(self, k: int, prices: List[int]) -> int:
        # 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

        # 同一天,对应着(1 + 2 * k)个状态,也就是(1 + 2 * k)个dp值
        # dp[j]表示当天处在状态j时,对应的最大利润
        dp = [0] * (1 + 2 * k)
        # 状态有如下:
        # 0:无操作,啥也没干
        # 1:从第1次买入开始持有的期间;2:从第1次卖出开始不持有的期间
        # 3:从第2次买入开始持有的期间;4:从第2次卖出开始不持有的期间
        # ...
        # 2*k-1:从第k次买入开始持有的期间;2*k:从第k次卖出开始不持有的期间
        
        # 初始化第0天
        # 状态0,以及偶数状态:在第0天对应dp值都为0,不用再手动初始化
        # 奇数状态:在第0天对应dp值都为买入第0天股票后的现金额
        for j in range(1, 2*k, 2):
            dp[j] = -prices[0]
        
        # 状态转移
        for i in range(1, len(prices)): # 遍历从索引1天开始的每一天
            price = prices[i]
            
            # 状态0列不需要转移
            # 奇数状态j:对应从当次买入开始持有的期间
            # 要么前一天已经是j,要么前一天是j-1今天买入(减去当天股价)变成j
            # 偶数状态j:对应从当次卖出开始不持有的期间
            # 要么前一天已经是j,要么前一天是j-1今天卖出(加上当天股价)变成j
            # 因此可以统一为:用(-1) ** j控制当天股价到底是减还是加!
            old_dp = dp[:] # 保证在更新过程中一直引用前一次的dp值
            for j in range(1, 2 * k + 1): # 从状态1开始遍历更新dp
                dp[j] = max(old_dp[j], old_dp[j-1] + (-1) ** j * prices[i])

        return dp[-1]
        # 或者标准一点
        # return max(dp)

5. 买卖股票含冷冻期(LC 309)

限制:

  • 可以进行 无限次 交易,但每次卖出后的下一天不能立即买入,必须空过 1 天(冷冻期)。
  • 没有交易次数上限,也没有手续费。

常见 DP 设计(三种状态):

  • dp[i][0]​: 第 i​ 天持股的最大收益
  • dp[i][1]​: 第 i​ 天不持股,且当天正好卖出(处于冷冻日)的最大收益
  • dp[i][2]​: 第 i​ 天不持股,且不处于冷冻期的最大收益

代码模板:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        if n < 2:
            return 0

        # 定义三种状态的动态规划数组
        dp = [[0] * 3 for _ in range(n)]
        dp[0][0] = -prices[0]  # 持有股票的最大利润
        dp[0][1] = 0           # 不持有股票,且处于冷冻期的最大利润
        dp[0][2] = 0           # 不持有股票,不处于冷冻期的最大利润

        for i in range(1, n):
            # 当前持有股票的最大利润等于前一天持有股票的最大利润或者前一天不持有股票且不处于冷冻期的最大利润减去当前股票的价格
            dp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i])
            # 当前不持有股票且处于冷冻期的最大利润等于前一天持有股票的最大利润加上当前股票的价格
            dp[i][1] = dp[i-1][0] + prices[i]
            # 当前不持有股票且不处于冷冻期的最大利润等于前一天不持有股票的最大利润或者前一天处于冷冻期的最大利润
            dp[i][2] = max(dp[i-1][2], dp[i-1][1])

        # 返回最后一天不持有股票的最大利润
        return max(dp[-1][1], dp[-1][2])

对比说明

  • 相比“股票 II”只是额外区分“卖出当天”与“非冷冻不持股”的两种状态,保证“卖出的第二天无法立即买入”。
  • 不需要记录“交易次数”维度,原因是可交易无限次

6. 买卖股票含手续费(LC 714)

限制:

  • 可以进行 无限次 交易,但每笔交易要支付手续费(假设一次交易指一次买+一次卖)。
  • 设定好是每次“买入”或者“卖出”时统一减去 fee​即可(整个单次买入卖出交易只减去一次手续费)
  • 这里设定买入时付手续费,卖出时不用

代码(与“股票 II”相似,只是卖出或买入时要多减一个 fee​):

class Solution:
    def maxProfit(self, prices: List[int], fee: int) -> int:
        # 设定买入时付手续费,卖出时不用
        dp0 = 0 # 当天不持有股票,第0天初始化为0
        dp1 = -prices[0] - fee # 当前持有股票,第0天初始化为-prices[0] - fee,相当于买入且付手续费

        for i in range(1, len(prices)):
            price = prices[i]
            old_dp0 = dp0
            dp0 = max(old_dp0, dp1 + price) # 今天不持有,则要么昨天不持有,要么昨天持有今天卖出
            dp1 = max(dp1, dp0 - price - fee) # 今天持有,则要么昨天持有,要么昨天不持有今天买入
        return max(dp0, dp1)

对比说明

  • 对比“股票 II”只是在“卖出”或者“买入”那一步额外扣 fee​。
  • 同样不需要记录“交易次数”维度,因为是无限次交易。

7. 总体比较

  1. 交易次数有限制(“最多 1 次”“最多 2 次”“最多 k 次”)时:

    • 都要在 DP 中增加一维记录 “第几次交易” ,或等价地把交易过程拆分成多个阶段。
    • 例如 LC 121(一次交易)虽然可以用 O(n) 解法,但是若用通用 DP,只是特例;LC 123(2 次),LC 188(k 次)。
  2. 交易次数无限但有其他限制时(冷冻期、手续费):

    • 不用那一维“交易次数”,而是在“是否持股”之外再增加状态或增加扣费逻辑
    • 比如 LC 309(冷冻期)会多一个或两个状态区分“冷冻日/非冷冻日”;LC 714(手续费)会在卖出时扣 fee​。
  3. 股票 II(无限次) vs 股票 III/IV(有限次)

    • 后者一定需要“交易次数”维度;前者不用。
  4. 冷冻期 / 手续费

    • 都是不限制交易次数,但要在转移方程里考虑“多等 1 天”或“多扣手续费”的限制。
  • 增加了什么:一旦题目说“有冷冻期”或者“有交易次数上限”等,就要在状态维度或转移中多出相应的逻辑;
  • 减少了什么:如果题目取消交易次数限制,或者去掉冷冻期/手续费等特殊条件,状态就会更精简。

总结

  • 股票 I (LC 121) :只允许 1 次交易 → 直接线性扫描或简单 DP。
  • 股票 II (LC 122) :无限次交易 → 用 dp[i][0 or 1]​ 即可。
  • 股票 III (LC 123)股票 IV (LC 188) :有限次交易(最多 2 次 / k 次) → 需要多加一维记录交易次数:dp[i][j][0 or 1]​。
  • 股票含冷冻期 (LC 309) :无限次交易 + 冷冻期 → 不需要记录次数,但要在状态中区分“刚卖出”的冷冻状态。
  • 股票含手续费 (LC 714) :无限次交易 + 手续费 → 在卖出操作时扣费,状态依旧“持股/不持股”两维就够。

这些模板互相对比,核心区别就在:

  • 是否有限次交易? → 需不需要 “交易次数” 维度;
  • 是否有额外限制(冷冻期/手续费)? → 在“持股/不持股”状态的基础上,增加一两个状态或扣费逻辑。

希望这些示例与讲解,能帮助你快速掌握和对比各种“股票问题”的通用 DP 思路

你可能感兴趣的:(Mophead的小白刷题笔记,leetcode,python,代码随想录,动态规划)