钢条切割DP递归解法正确但加记忆化结果错误,记忆化适用于所有DP吗?
记忆化版本出错的核心原因
你的记忆化状态设计存在根本性问题:
你给helper2设计的参数包含size、index、profit三个可变变量,但缓存用的dp数组只记录了index和size两个维度的状态,完全忽略了profit的影响。你的递归逻辑里,函数返回值是「当前累计profit + 剩余长度可获得的最大新增利润」,这就意味着同一个(index, size)组合,传入的profit不同,最终返回值也完全不同。你缓存了第一次调用该状态的返回值,后续遇到相同(index, size)但profit更高的情况时,直接返回了缓存的错误值,最终结果自然偏小。
以你的测试用例为例,就是某一个(index, size)状态第一次调用时传入的profit比后续正确路径的profit少1,缓存了错误的48相关结果,最终输出比正确值小1的48。
正确的记忆化改造方案
记忆化缓存的状态必须满足「同一状态的返回值固定,和到达路径无关」,所以你需要把累计的profit从参数中移除,改成让递归函数直接返回「剩余长度为size、处理到第index个切割项时,能获得的最大利润」,累计逻辑放到递归返回时计算即可,修改后的代码如下:
// 修正后的递归+记忆化解法 int helper2(vector<int> &price, int size, int index, vector<vector<int>> &dp) { if (size == 0) return 0; if (size < 0 || index == price.size()) return INT_MIN; if (dp[index][size] != -1) return dp[index][size]; // 选择当前切割长度,还可以继续选当前长度 int take = INT_MIN; if (size >= index + 1) { take = price[index] + helper2(price, size - (index + 1), index, dp); } // 不选当前切割长度,跳到下一个 int notake = helper2(price, size, index + 1, dp); return dp[index][size] = max(take, notake); } int cutRod2(vector<int> &price) { int n = price.size(); vector<vector<int>> dp(n + 1, vector<int>(n + 1, -1)); return helper2(price, n, 0, dp); }
修改后运行测试用例就会输出正确的49。
记忆化的适用范围
记忆化不是对所有DP问题都适用,只有满足以下两个核心条件的问题才能用记忆化优化:
- 存在重叠子问题:同一个子问题会被重复调用多次,缓存才有价值,否则反而会额外消耗存储资源
- 满足无后效性:某个状态的最优解只和其子状态的最优解有关,和到达该状态的路径无关。你原来的写法就是把路径上的累计利润作为参数传入,破坏了无后效性,才导致缓存失效。
除此之外,如果问题的状态维度极高,记忆化的空间开销会超出系统限制,这种场景下也不适合使用记忆化,更推荐用递推式DP甚至滚动数组优化的递推写法。
内容的提问来源于stack exchange,提问作者Premnaath Varadharajan
相关产品推荐
相关产品推荐

