为最长递增子序列(LIS)解法添加缓存后结果错误,请求排查问题
问题分析与修复方案
你的缓存版本出现错误的核心原因是递归状态的定义和缓存key不匹配,而且原递归函数依赖传递的res参数来累积长度,这导致同一prev_i和curr_i的状态下,不同的res值会被错误复用缓存结果。
具体问题拆解
在无缓存的版本中,res是随调用路径传递的累计长度,不同路径到达同一个prev_i和curr_i时,res可能不一样。但你添加缓存时,只把prev_i+1和curr_i+1作为缓存key,完全忽略了res的影响——这就导致后续调用同一prev_i、curr_i时,会直接返回之前存储的、基于另一个res值计算出的结果,最终得到错误的输出。
比如在测试用例[5,4,19,5,7,12]中,当递归到prev_i=2(对应元素19)、curr_i=3(对应元素5)时,原缓存版本会返回当前的res值(此时可能是3),但另一条路径到达这个状态时res值不同,缓存的结果就会干扰正确计算,最终导致输出3而不是预期的4。
修复思路
我们需要重构递归函数,让它的返回值直接表示从prev_i(上一个选中元素的索引)和curr_i(当前遍历位置)开始,能得到的最长递增子序列长度,不再依赖外部传入的res参数。这样,缓存的key(prev_i+1和curr_i+1)就能唯一确定状态,不会受调用路径影响。
修正后的代码
var recur = function(dp, ns, prev_i, curr_i) { // 遍历完所有元素,没有更多可选,返回0 if (curr_i >= ns.length) { return 0; } // 检查缓存,存在则直接返回 if (prev_i !== -1 && dp[prev_i + 1][curr_i + 1] !== undefined) { return dp[prev_i + 1][curr_i + 1]; } // 情况1:选择当前元素(满足递增条件时) let take = 0; if (prev_i === -1 || ns[curr_i] > ns[prev_i]) { take = 1 + recur(dp, ns, curr_i, curr_i + 1); } // 情况2:不选择当前元素 let notTake = recur(dp, ns, prev_i, curr_i + 1); // 计算当前状态的最大值并缓存 const maxVal = Math.max(take, notTake); if (prev_i !== -1) { dp[prev_i + 1][curr_i + 1] = maxVal; } return maxVal; }; var lengthOfLIS = function(ns) { if (ns.length === 0) return 0; if (ns.length === 1) return 1; // 初始化dp数组,处理prev_i=-1的情况(转成索引0) const dp = Array(ns.length + 1) .fill(undefined) .map(() => Array(ns.length + 1).fill(undefined)); // 初始调用:还未选择任何元素(prev_i=-1),从第一个元素开始遍历 return recur(dp, ns, -1, 0); };
为什么这样能解决问题?
- 递归函数的返回值是当前状态下的最长长度,而非依赖外部传递的累积值,每个
prev_i和curr_i的状态对应唯一的最长长度,缓存不会再出错。 - 对于测试用例
[5,4,19,5,7,12],修正后的代码会正确计算出最长递增子序列(比如[4,5,7,12])的长度4。
内容的提问来源于stack exchange,提问作者kenpeter
相关产品推荐
相关产品推荐

