关于JavaScript回溯算法的困惑:排列问题执行流程疑问
我近期在学习JavaScript中的回溯算法时感到十分困惑,尤其聚焦于排列问题——给定一个数组,生成其所有可能的排列。以下是相关实现代码:
function permute(nums) { let result = []; function backtrack(temp = []) { if (temp.length === nums.length) { result.push([...temp]); return; } for (let i = 0; i < nums.length; i++) { if (temp.includes(nums[i])) continue; temp.push(nums[i]); backtrack(temp); temp.pop(); } } backtrack(); return result; }
为简化分析,我以nums = [1,2]为例。我的理解是:初始temp为空数组,当i=0时temp变为[1],调用backtrack寻找剩余值2;随后i=1时temp变为[1,2],满足长度条件,将该数组的副本加入result。我的疑问是:将[1,2]加入result后,后续代码的执行流程是怎样的?
已知这段代码能正确返回[[1,2],[2,1]],但我无法理解其逻辑。我以为加入[1,2]后,temp会弹出最后一个元素变为[1],然后循环重新执行,但这样会再次添加2并重复判断长度,无法得到[2,1]的结果。我知道自己的理解存在偏差,但不清楚问题出在哪里。
执行流程拆解(以nums=[1,2]为例)
咱们一步步拆解每一步的执行逻辑:
初始调用:执行
backtrack(),此时temp = [],进入循环i=0:temp不包含1,执行temp.push(1),temp变为[1],调用backtrack([1])。
第一次递归调用:
backtrack([1]),temp.length=1不等于2,进入循环:i=0:temp.includes(1)为真,跳过当前循环;i=1:temp不包含2,执行temp.push(2),temp变为[1,2],调用backtrack([1,2])。
第二次递归调用:
backtrack([1,2]),temp.length=2等于nums.length,执行result.push([...temp]),result变为[[1,2]],随后return退出当前函数。回到第一次递归的i=1分支:
- 退出
backtrack([1,2])后,执行temp.pop(),temp变回[1]; - 循环
i=1执行完毕,整个循环结束,退出第一次递归的backtrack函数。
- 退出
回到初始调用的i=0分支:
- 退出第一次递归后,执行
temp.pop(),temp变回[]; - 循环继续执行
i=1:temp不包含2,执行temp.push(2),temp变为[2],调用backtrack([2])。
- 退出第一次递归后,执行
第三次递归调用:
backtrack([2]),temp.length=1不等于2,进入循环:i=0:temp不包含1,执行temp.push(1),temp变为[2,1],调用backtrack([2,1])。
第四次递归调用:
backtrack([2,1]),temp.length=2等于nums.length,执行result.push([...temp]),result变为[[1,2],[2,1]],return退出当前函数。回到第三次递归的i=0分支:
- 退出后执行
temp.pop(),temp变回[2]; i=1:temp.includes(2)为真,跳过;- 循环结束,退出第三次递归函数。
- 退出后执行
回到初始调用的i=1分支:
- 退出后执行
temp.pop(),temp变回[]; - 循环结束,退出初始的backtrack函数;
- 最终返回
result,即[[1,2],[2,1]]。
- 退出后执行
你的误区在于:误以为从[1,2]返回后,会在第一次递归的循环里重复执行,但实际上第一次递归的循环i已经走到末尾,执行完pop后直接退出该递归函数,回到初始调用的循环,继续执行i=1的分支——这正是生成[2,1]的核心步骤。
内容的提问来源于stack exchange,提问作者ContravariantMind

