数组目标和递归求解代码逻辑解析:递归调用与组合清理疑问
组合总和问题代码解析
我能理解这段找数组中和为目标值所有组合代码的基础逻辑,但搞不懂递归怎么找到更多组合,也不清楚递归结束后combination数组是怎么被“清理”的。试过桌面模拟和ChatGPT,还是没完全搞懂整体运行机制,核心困惑在for循环里的递归调用,以及为什么有时候i和start的值不一样。
原代码(注:代码存在一处缺失,正确逻辑需添加push步骤)
function findSums(nums, target) { function findSum(target, start, combination) { if (target === 0) { result.push([...combination]); return; } for (let i = start; i < nums.length; i++) { console.log('i: ',i,'start: ',start,'n1:',nums[i],'n2:',nums[i-1]); if (i > start && nums[i] === nums[i - 1]) { continue; } // 原代码缺失:combination.push(nums[i]); findSum(target - nums[i], i + 1, combination); combination.pop(); } } nums.sort((a, b) => a - b); let start = 0; let combination = []; let result = []; findSum(target, start, combination); return result; } console.log(findSums([1, 3, 2, 4, 5], 6));
核心逻辑拆解
1. 代码目标:找不重复的元素组合
这段代码的作用是找出数组中元素不重复使用、组合本身也不重复的所有和为目标值的组合。数组先排序是为了方便去重和按顺序遍历。
2. 递归+回溯的运行机制
递归在这里是用来深度遍历所有可能的组合路径,而combination.pop()是回溯的核心清理步骤,两者配合遍历所有合法组合:
递归参数的意义
target:当前还需要凑齐的数值start:当前循环从数组的哪个索引开始选元素(避免生成[1,2]和[2,1]这种重复组合)combination:当前正在构建的组合数组(复用同一个数组,节省内存)
for循环与递归的配合
循环从start而非0开始,是为了保证组合元素按排序后的顺序选取,不会回头选之前的元素,从根源避免重复组合。
以排序后的数组[1,2,3,4,5]、目标值6为例,走一遍核心流程:
- 第一层循环
start=0,i=0,先把1加入combination(原代码缺失的push步骤),递归调用findSum(5, 1, [1])。 - 第二层循环
start=1,i=1,把2加入combination,递归调用findSum(3, 2, [1,2])。 - 第三层循环
start=2,i=2,把3加入combination,此时target=3-3=0,满足条件,把[1,2,3]的拷贝存入result,返回。 - 回到第三层循环,执行
combination.pop(),combination变回[1,2],i递增到3,尝试选4:target=3-4=-1,递归里不满足条件,返回后pop,combination还是[1,2];i再递增到4,选5同理,返回后pop,第三层循环结束。 - 回到第二层循环,执行
combination.pop(),combination变回[1],i递增到2,尝试选3:target=5-3=2,递归里从start=3选元素,都凑不齐2,返回后pop;i继续递增到3选4、4选5,直到第二层循环结束。 - 回到第一层循环,执行
combination.pop(),combination变回空数组,i递增到1选2,重复上述流程,最终找到[2,4];i递增到4选5,找到[1,5]。
为什么i会和start不同?
- 同一层循环中,i从start开始逐步递增,所以随着循环推进,i会大于start,这时候如果遇到和前一个元素相同的数值(
nums[i] === nums[i-1]),就会跳过,避免生成重复组合。比如数组有[1,1,2],第一层start=0,i=0处理第一个1后,i=1时i>start且nums[1]和nums[0]相同,直接跳过,避免重复生成[1,2]组合。 - 递归进入下一层时,start会被设置为
i+1,意味着下一层只能从当前元素的下一个位置选元素,保证每个元素只被使用一次。
3. combination的“清理”逻辑:回溯的关键
combination.pop()就是清理操作的核心:每次递归返回后,把当前加入组合的元素移除,让组合数组回到上一层递归调用前的状态,这样就能继续尝试同一层循环的下一个元素。
比如你选了元素A,递归处理完所有包含A的组合后,把A从组合里拿掉,再选元素B,处理所有包含B的组合,以此类推,复用同一个数组完成所有组合的构建,不用每次创建新数组,效率更高。
修正后的完整代码
function findSums(nums, target) { function findSum(target, start, combination) { if (target === 0) { result.push([...combination]); return; } for (let i = start; i < nums.length; i++) { if (i > start && nums[i] === nums[i - 1]) { continue; } combination.push(nums[i]); // 补上缺失的push步骤 findSum(target - nums[i], i + 1, combination); combination.pop(); } } nums.sort((a, b) => a - b); let result = []; findSum(target, 0, []); return result; } console.log(findSums([1, 3, 2, 4, 5], 6)); // 输出:[[1,2,3],[1,5],[2,4]]
内容的提问来源于stack exchange,提问作者Erick Armijos
相关产品推荐
相关产品推荐

