labuladong刷题之 股票问题大集合

今日收获,学习时长:

总结 Leetcode全部股票交易系列问题:

以第188题为模版:给你一个整数数组的股价和k,要求最多k次交易以内,买k次,卖k次 来获得最大利润

121题:只进行1次交易 k=1;122是不限交易次数,相当于 k = +infinity(正无穷);

123题只进行 2 次交易,相当于 k = 2;309和714题也是不限次数,但是加了交易「冷冻期」和「手续费」的额外条件,其实就是第二题的变种,都很容易处理。

具体的动态规划推导过程:

步骤1 如何穷举

动态规划算法本质上就是穷举「状态」,然后在「选择」中选择最优解。

for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 择优(选择1,选择2...)

如说这个问题,每天都有三种「选择」:买入、卖出、无操作,我们用 buysellrest 表示这三种选择。那么 rest 操作还应该分两种状态,一种是 buy 之后的 rest(持有了股票),一种是 sell 之后的 rest(没有持有股票)。

这个问题的「状态」有三个,第一个是天数,第二个是允许交易的最大次数,第三个是当前的持有状态(即之前说的 rest 的状态,我们不妨用 1 表示持有,0 表示没有持有)。

dp[i][k][0 or 1]
0 <= i <= n - 1, 1 <= k <= K
n 为天数,大 K 为交易数的上限,0 和 1 代表是否持有股票。
此问题共 n × K × 2 种状态,全部穷举就能搞定。

for 0 <= i < n:
    for 1 <= k <= K:
        for s in {0, 1}:
            dp[i][k][s] = max(buy, sell, rest)

步骤2状态转移框架

状态公式含义
不持股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])今天持股,要么昨天就持股,要么今天刚买入

你可以这样理解整个转移过程:

“我今天的状态,要么是从昨天保持下来的,要么是因为今天买/卖而产生变化的。”

所以这两行转移方程的意义其实就是:

  • 不持股:要么休息,要么卖出;

  • 持股:要么休息,要么买入。

步骤3 base case,完善状态转移方程

dp[-1][...][0] = 0
解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0。

dp[-1][...][1] = -infinity
解释:还没开始的时候,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。

dp[...][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0。

dp[...][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。
状态含义现实意义初始化值理由
dp[-1][k][0]第 -1 天、不持股还没开始,手里没股票0利润为 0
dp[-1][k][1]第 -1 天、持股还没开始不可能持股-∞不可能的状态
dp[i][0][0]不允许交易、不持股没买卖机会0利润为 0
dp[i][0][1]不允许交易、持股没买卖机会还持股-∞不可能状态

你只需要记住下面这个现实逻辑:

条件是否可能持股dp 值
没开始(i=-1)❌ 不可能-∞
不允许交易(k=0)❌ 不可能-∞
其他情况不持股✅ 可以(利润0)0

再配合下面这句话:

“不能发生的状态 → 设为 -∞;
什么都没发生的状态 → 设为 0。”

思考过程中哪些要注意的点:

1 为什么只有买入的地方k才会变化

dp[i][k][0] → 第 i 天,不持股,最多可进行 k 次交易的最大利润 dp[i][k][1] → 第 i 天,持股,最多可进行 k 次交易的最大利润

这里的 “k” 表示:

到今天为止,你最多还能进行几次完整的「买+卖」交易。

也就是说,k 不是「已经用掉几次」,而是「还剩几次可以用」。

🎯 核心逻辑区分:「买」才消耗交易次数
动作是否消耗一次交易额度(k)原因
买入 (buy)✅ 是一次交易的开始,需要消耗一个名额
卖出 (sell)❌ 否交易的结束,消耗早已在“买入”那天计入

2 数学定义中的base case和现实实现中的base case-如何修改?

这段代码是在定义“第 0 天”的状态,
但它的数值是根据“第 -1 天的理论 base case”推导出来的。

也就是说:

  • i = -1理论上的起点(数学边界)

  • i = 0程序中的第一个可计算状态

  • 这段代码就是把理论上的 dp[-1] 的含义,在程序中具体实现出来

(一句话逻辑总结(你只要记住这个!)

当 i == 0 时,别套公式,直接想现实:

  • 第一天不买 → 利润 0

  • 第一天买入 → 利润 = -prices[0])

想象你有一个公式可以算「昨天的利润」:今天利润 = max(昨天利润, 昨天持股 + 今天卖出)

但如果“昨天”是第 0 天之前(即第 -1 天),那你根本没进入市场。

所以你不能直接“用昨天的值”,而必须手动告诉程序:

“第 0 天开始前,我的利润是 0,我没有股票。”

所以第 0 天的 base case 不是修改原有逻辑
而是对「dp[-1][...] 的理论定义」进行实际展开计算的结果。

层面形式作用
理论dp[-1][k][0] = 0, dp[-1][k][1] = -∞让状态转移公式统一、简洁、通用
实现特判 i == 0,显式赋值避免数组越界,体现理论 base case 的含义

3 “如果昨天不持股、还剩 k−1 次机会,今天买入又消耗一次,那不是应该变成 k−2 吗?为什么变成 k 呢?”

🔹 k 表示「最多可完成的交易次数」——不是“还剩多少次”。
🔹 dp[i-1][k-1][0] 表示「前 i−1 天最多完成 k−1 次交易且不持股」;
🔹 今天买入,开始第 k 次交易,所以转移到 dp[i][k][1]

因此:dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]);

举例:昨天是1次 今天第二次

“前面最多完成 1 次交易、现在不持股”的状态
转移到
“现在开始第 2 次交易、持股”的状态。

后续套公式使用该套路的时候的总结:

状态转移方程总结

base case:
dp[-1][...][0] = dp[...][0][0] = 0
dp[-1][...][1] = dp[...][0][1] = -infinity

状态转移方程:
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])

没错!整个股票系列题的“灵魂”状态转移方程几乎不变,只是 base case 和约束条件(k、fee、冷冻期等)不同。

我来一步步带你建立这种“模块化思维”,
让你以后看到任何股票题都能一眼写出框架,而不用背公式。

变化的部分:

题号限制如何修改
121最多 1 次交易 (k=1)去掉 k 维,简化为二维;dp[i][1] = max(dp[i-1][1], -prices[i])
122无限次交易 (k=∞)不需要关心交易次数,直接用 dp[i][0] = max(dp[i-1][0], dp[i-1][1]+price[i])
123最多 2 次交易 (k=2)保留 k 维,初始 dp[i][0][*] = base case
188最多 k 次交易同上,只是外层多一层循环控制 k
309含冷冻期卖出后需多加一个“冻结状态”:dp[i][0] = max(dp[i-1][0], dp[i-1][1]+price[i-1]) 改成依赖 dp[i-2][0]
714含手续费在卖出时扣手续费:dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + price[i] - fee)

base case 模板(通用逻辑)

情况初始化理由
i = -1(还没开始)dp[-1][k][0] = 0, dp[-1][k][1] = -∞没操作不可能持股
k = 0(不能交易)dp[i][0][0] = 0, dp[i][0][1] = -∞没买卖资格也不可能持股
i = 0(第 1 天)手动计算:dp[0][0]=0, dp[0][1]=-prices[0]第一日只能选择买或不买

✅ 你不需要死记,只要理解「不可能 → -∞」、「没操作 → 0」这两个原则。

具体操作练习的部分:

1 121 和122题的区别:

121:买入方程写死为 -prices[i] → 只能买一次。
122:买入方程依赖 dp[i-1][0] → 可以卖完再买。

➜ 虽然都省略了 k,但是否能“复用 dp[i-1][0]”
就决定了交易次数的逻辑不同。

🧩 在框架中:
k 控制交易上限,是 DP 的“第三维”;

🧠 在实现中:

  • 如果 k 对状态有影响 → 保留;

  • 如果 k 是固定(1 或 ∞)→ 去掉,不影响结果。

想象「k」是你拥有的「买卖许可卡」:

  • 121 题: 你只有 1 张 → 写死;

  • 122 题: 你有无限张 → 根本不用考虑;

  • 123/188 题: 你有有限几张 → 每次买入都要减一张;

  • 309/714 题: 你有无限张,但有别的限制(冷冻期、手续费)。

2 122题和309区别:

- 增加了base case i=2的情况:

前两天是“系统刚启动”的两天,市场里还没有卖出产生冷冻期,
所以要手动定义最初两天的状态,让之后的“冷冻期规则”有现实基础。

想象你是一个新交易者:

  • 第 0 天:开账户,看市场,你可以选择“先买”。

  • 第 1 天:你可能卖出第一次股票,但市场的“冷冻系统”还没激活(因为刚卖完还没轮到冷冻)。

  • 第 2 天:系统才会真正检测冷冻(“昨天卖过,今天不能买”)。系统自动判断「昨天是否卖出 → 今天能否买」。

因此:初始化第 0、1 天 = 手动建立交易记录和利润计算的初始条件,让“冷冻机制”从第 2 天起能自动生效。如果不算前两天,就像一个新系统“忘记了账户历史”,无法判断今天的买卖是否合法。

-买入改成dp[i-2]:因为间隔了一天才买入 相比较昨天状态买入需修改为前天状态买入

3 第714题

和122题很像 都是k为正无穷 只是在这个基础上加上了每次交易要付手续费

每次交易要支付手续费,只要把手续费从利润中减去即可,改写买入方程即可

4 第123题 最多交易2次 规定了k=2的情况  所以k的情况递减

还记得前面总结的「穷举框架」吗?就是说我们必须穷举所有状态。其实我们之前的解法,都在穷举所有状态,只是之前的题目中 k 都被化简掉了。这里需要穷举k的所有状态和情况:

那为什么我使用 k = max_k, k-- 的方式呢?因为这样符合语义:

你买股票,初始的「状态」是什么?应该是从第 0 天开始,而且还没有进行过买卖,所以最大交易次数限制 k 应该是 max_k;而随着「状态」的推移,你会进行交易,那么交易次数上限 k 应该不断减少,这样一想,k = max_k, k-- 的方式是比较合乎实际场景的。

5 188题 最多k次交易

你一共 n 天,可以发生几次交易?

思考一下:

  • 如果你有 n 天的价格序列 [p₀, p₁, p₂, ..., pₙ₋₁]

  • 每次交易至少要花 2 天(一天买,一天卖)

➡️ 所以在 n 天内,最多能完成的交易次数是:

⌊ n / 2 ⌋

当 k ≥ n/2 时,
交易次数不再是瓶颈 → 相当于「无限次交易」,
直接使用 122 题的 O(n) 解法即可。

  • base case 写法与 123 题相同;

  • 唯一不同在于 k 可变,需循环 k;

  • 并要加上 if (k >= n/2) 优化分支;

  • 最终答案仍为 dp[n-1][k][0](最后一天不持股的最大利润)。

总结:

关键就在于列举出所有可能的「状态」,然后想想怎么穷举更新这些「状态」。一般用一个多维 dp 数组储存这些状态,从 base case 开始向后推进,推进到最后的状态,就是我们想要的答案。想想这个过程,你是不是有点理解「动态规划」这个名词的意义了呢?

具体到股票买卖问题,我们发现了三个状态,使用了一个三维数组,无非还是穷举 + 更新,不过我们可以说的高大上一点,这叫「三维 DP」,听起来是不是很厉害?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值