news 2026/6/10 10:35:08

LeetCode 188. Best Time to Buy and Sell Stock IV - 三维DP详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 188. Best Time to Buy and Sell Stock IV - 三维DP详解

题意 + 思路一句话概括:这是「最多进行 k 次交易」的股票买卖问题,可以用三维 DP:dp[day][transaction][hold]dp[day][transaction][hold]dp[day][transaction][hold],其中交易数按「卖出次数」计数,买入不加 1,卖出才加 1。leetcode​

题目与难点

给定整数 k 和数组 prices,prices[i] 表示第 i 天的股价,要求在至多 k 笔交易内获得最大利润。leetcode​
一次交易 = 一次买入 + 一次卖出,不能同时持有多笔仓位(必须卖了才能再买)。leetcode​
这题的难点在于:
既要限制交易次数,又要正确处理「持股/不持股」状态。
朴素暴力会爆炸,需要用状态机 DP 来精确刻画每天的选择。

状态设计与含义

采用三维 DP:
dp[i][j]dp[i][j]dp[i][j]:第 iii 天结束,已经完成了 jjj 次交易,且当前 不持股 的最大利润。
dp[i][j][2]dp[i][j][2]dp[i][j][2]:第 iii 天结束,已经完成了 jjj 次交易,且当前 持股 的最大利润。
关键约定:
j 表示的是「完成的交易数 = 完成的 卖出次数」。
买入:不改变 j。
卖出:使 j 从 j−1j-1j−1 变成 jjj。
这样定义带来的好处:
交易次数的增加只发生在卖出动作上,语义清晰。
约束「至多 k 次交易」就变成了 0 <= j <= k。

初始化细节

设:
day_count = pricesSize。
transaction_count = k + 1(因为交易数 j 取值范围是 0…k,一共 k+1 种)。leetcode​
hold_count = 2(0 表示不持股,1 表示持股)。
初始化三维数组 dp[day_count][transaction_count][2],全部置为一个很小的值 INF_MIN,表示「不可能的状态」。leetcode​
第 0 天:
不持股:
dp=0dp = 0dp=0:第 0 天结束,不持股,且尚未完成任何交易,利润为 0。
对于 j>0j > 0j>0,dp[0][j][0] = INF_MIN:第 0 天不可能完成任何卖出,所以这些都是非法状态。
持股:
dp[2]=−pricesdp[2] = -pricesdp[2]=−prices:如果第 0 天结束时选择持股,那么唯一方式就是第 0 天买入一次,利润为 −prices-prices−prices。
对于 j>0j > 0j>0,dp[0][j][1] = INF_MIN:第 0 天不可能既持股又已经完成卖出。
注意:
状态存在,并不代表一定会选;最终答案是从所有可能状态取最大值。

状态转移方程

在第 i 天(i >= 1),枚举所有交易数 j(0…k):

不持股状态

dp[i][j]={dp[i−1],j=0max⁡(dp[i−1][j], dp[i−1][j−1][2]+prices[i]),j>0dp[i][j] = \begin{cases} dp[i-1], & j = 0 \ \max\big(dp[i-1][j],\ dp[i-1][j-1][2] + prices[i]\big), & j > 0 \end{cases}dp[i][j]={dp[i−1],max(dp[i−1][j], dp[i−1][j−1][2]+prices[i]),j=0j>0
直观解释:
j == 0:
还没有完成任何交易,所以今天不可能「卖出」;只能继承「昨天也不持股」。
j > 0:
要么昨天就不持股并且已经完成了 j 次交易,今天什么都不做;
要么昨天持股并完成了 j-1 次交易,今天卖出获得 prices[i],完成第 j 次交易。
这正体现了「卖出时交易数 +1」的逻辑。

持股状态

dp[i][j][2]=max⁡(dp[i−1][j][2], dp[i−1][j]−prices[i])dp[i][j][2] = \max\big(dp[i-1][j][2],\ dp[i-1][j] - prices[i]\big)dp[i][j][2]=max(dp[i−1][j][2], dp[i−1][j]−prices[i])
解释:
昨天已经持股且完成了 j 次交易,今天什么都不做;
昨天不持股并完成了 j 次交易,今天买入一股,交易数不变,但现金少了 prices[i]。
注意这里的关键点:
买入不改变 j。
不能从 dp[i-1][j][1] - prices[i] 来转移,否则变成「在已经持股的基础上再买一次」,违反「不能同时持有多份股票」的约束。

最终答案与遍历顺序

当所有天数都算完后:
最后一天必须是 不持股 状态才能保证利润已经完全兑现。
遍历所有 0 <= j <= k:
ans=max⁡0≤j≤kdp[day_count−1][j]\text{ans} = \max_{0 \le j \le k} dp[day_count-1][j]ans=0≤j≤kmaxdp[day_count−1][j]
遍历顺序建议:
外层:天数 i 从 1 到 day_count - 1。
中层:交易数 j 从 0 到 k。
边界处理:
当 j == 0 时,计算 dp[i][0][0] 不能访问 dp[i-1][-1][1],所以单独特判。
其余情况按状态转移方程即可。

时间复杂度与空间优化

在当前三维 DP 实现中:
状态数量:day_count * (k+1) * 2,时间复杂度 O(nk)O(nk)O(nk)。
空间复杂度同样为 O(nk)O(nk)O(nk),因为 day、transaction、hold 都作为维度存在。
优化方向:
滚动数组:
转移中 dp[i] 只依赖 dp[i-1],可以把 day 这一维压掉,用 dp[transaction_count][2] 来存当前天,整体空间降为 O(k)O(k)O(k)。
大 k 特判:
若 k >= pricesSize / 2,则限制形同虚设,可以视为「不限次数交易」问题,直接用贪心:只要 prices[i] > prices[i-1] 就把差额加入利润。leetcode​

对应 C 实现要点(与你的代码对齐)

你的代码核心部分与上述思路一致,关键点包括:leetcode​
三维 dp 的 malloc 与初始化:
外三层分别对应 day_count、transaction_count 和 hold_count。
每个元素先填充为 INF_MIN,代表不可能状态。
第 0 天初始化:
dp[0][0][NOT_HOLD] = 0;
dp[0][0][HOLD] = -prices[0];

转移:
不持股:
j==0:只能 dp[i][0][NOT_HOLD] = dp[i-1][0][NOT_HOLD];
j>0:

dp[i][j][NOT_HOLD]=MAX(dp[i-1][j][NOT_HOLD],dp[i-1][j-1][HOLD]+price);

持股:

dp[i][j][HOLD]=MAX(dp[i-1][j][HOLD],dp[i-1][j][NOT_HOLD]-price);

最终答案:

max_profit=INF_MIN;for(j=0;j<transaction_count;j++)max_profit=MAX(max_profit,dp[day_count-1][j][NOT_HOLD]);

结束后释放所有 malloc 的内存,避免内存泄漏。leetcode​

面试官视角下的典型追问

在面试场景中,如果你给出上述定义和转移,面试官很可能继续问你:
为什么交易次数用「卖出次数」来计,而不是买入次数?
如果要你把三维 DP 优化成二维 / 一维,如何改写代码?状态遍历顺序会有什么要求?
当 k 特别大(例如 k >= n/2)时,有没有必要继续用 O(nk)O(nk)O(nk) 的 DP?贪心解法是怎样的?leetcode​
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/?envType=study-plan-v2&envId=top-interview-150
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/submissions/1875439908/?envType=study-plan-v2&envId=top-interview-150

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/4 23:55:51

对比传统方案:FLV.JS如何提升视频开发效率10倍

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 生成一个对比测试页面&#xff0c;分别使用FLV.JS和传统video标签实现相同功能的视频播放器&#xff0c;要求&#xff1a;1.相同UI界面设计 2.性能指标对比图表 3.内存占用监控 4.…

作者头像 李华
网站建设 2026/6/6 0:27:52

传统调试vsAI:解决403 Token错误效率提升300%

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 构建一个效率对比工具&#xff0c;包含&#xff1a;1. 传统调试流程模拟器 2. AI辅助调试流程 3. 耗时统计和对比可视化。实现两个并行工作流&#xff0c;分别展示逐步调试过程和A…

作者头像 李华
网站建设 2026/5/22 17:48:24

企业内网部署谷歌镜像的完整解决方案

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个企业级谷歌镜像系统&#xff0c;要求&#xff1a;1) 支持LDAP/AD账号登录认证 2) 记录所有搜索日志到MySQL数据库 3) 实现缓存机制提升响应速度 4) 管理员可以查看使用统计…

作者头像 李华
网站建设 2026/5/20 0:01:24

X-ANYLABELING在医疗影像分析中的实战案例

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个医疗影像标注系统&#xff0c;专门用于CT扫描中的肿瘤识别和标注。功能需求&#xff1a;1. 支持DICOM格式读取和显示&#xff1b;2. 提供2D切片和3D体积标注工具&#xff…

作者头像 李华
网站建设 2026/5/1 8:43:59

零基础入门:5分钟搭建你的第一个IP检测工具

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个极简的IP纯净度检测网页应用&#xff0c;适合新手学习。要求&#xff1a;1) 单页面设计 2) 输入框接收IP地址 3) 调用免费IP API获取基础数据 4) 显示简单检测结果(纯净/可…

作者头像 李华
网站建设 2026/6/10 9:55:46

企业级JDK下载与版本管理最佳实践

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个企业级JDK管理系统&#xff0c;功能包括&#xff1a;1. 内网镜像源自动同步官方JDK更新&#xff1b;2. 基于角色的下载权限控制&#xff1b;3. 版本使用情况统计看板&…

作者头像 李华