买卖股票III问题递归DP方案优化及代码超时原因排查
解决LeetCode「最佳买卖股票时机III」递归DP超时问题的分析与优化
一、超时原因分析
- 冗余状态参数:你的递归函数里传递了
buyIndex,但这个参数并未被纳入记忆化的状态集合——当前记忆化只记录了State、index、k,但不同的buyIndex会导致相同State/index/k的状态被重复计算,产生大量无效递归调用。 - 初始值判断错误:你用
0作为DP数组的初始值,但实际场景中最大利润可能就是0(比如股价持续下跌),这会让dp.get(s)[index][k] != 0的判断把有效0值当成未计算状态,触发重复递归。 - 递归分支冗余:在
BUY状态下的profit < 0判断完全多余——如果卖出会亏损,最优选择必然是继续持有,这个分支会额外增加不必要的递归调用。
二、自顶向下递归DP的优化方案
核心优化点
- 简化状态定义:移除
buyIndex参数,转而在「持有股票」状态的DP值中直接记录当前持有股票的最大利润(即买入成本的负值,卖出时加上当前股价即可得到实际利润),无需单独存储买入索引。 - 修正记忆化标记:用
null(而非0)作为未计算状态的标记,避免和有效0利润状态混淆。 - 精简递归分支:去掉不必要的亏损判断,让
Math.max自动选择最优分支,减少冗余调用。
优化后的代码
class Solution { // dp[hold][index][k]:hold=0未持有股票,1持有股票;index当前天数;k剩余可交易次数 Integer[][][] dp; int[] prices; public int maxProfit(int[] prices) { this.prices = prices; int n = prices.length; // 初始化记忆化数组,默认值为null表示未计算 dp = new Integer[2][n][3]; return dfs(0, 0, 2); } private int dfs(int index, int hold, int k) { // 终止条件:无剩余交易次数或遍历完所有天数 if (k == 0 || index == prices.length) { return 0; } // 已计算过直接返回 if (dp[hold][index][k] != null) { return dp[hold][index][k]; } int res; if (hold == 0) { // 未持有:要么继续观望,要么买入(消耗一次交易次数) res = Math.max( dfs(index + 1, 0, k), -prices[index] + dfs(index + 1, 1, k - 1) ); } else { // 持有:要么继续持有,要么卖出(不消耗交易次数,买入时已扣除) res = Math.max( dfs(index + 1, 1, k), prices[index] + dfs(index + 1, 0, k) ); } dp[hold][index][k] = res; return res; } }
优化后逻辑说明
- 状态更紧凑:用
hold(0/1)替代原有的State枚举,hold=1时的DP值直接表示持有股票的最大利润(比如买入价格为p,此时利润为-p,卖出时加当前股价prices[index]就是实际盈利),彻底消除buyIndex带来的冗余。 - 记忆化更准确:
Integer数组的null值明确标记未计算状态,有效避免了原代码中0值导致的重复计算问题。 - 递归逻辑更简洁:移除多余的亏损判断,让
Math.max自动选择最优操作,减少递归分支的无效调用。
内容的提问来源于stack exchange,提问作者tester test
相关产品推荐
相关产品推荐

