
本文详解数组全排列的递归实现原理与常见错误,重点纠正常见的“遗漏子排列遍历”问题,提供可直接运行的修正代码,并说明基础边界条件、递归结构设计及关键循环逻辑。
本文详解数组全排列的递归实现原理与常见错误,重点纠正常见的“遗漏子排列遍历”问题,提供可直接运行的修正代码,并说明基础边界条件、递归结构设计及关键循环逻辑。
全排列(Permutation)是经典的回溯/递归问题:给定一个无重复元素的数组,需生成其所有可能的元素顺序组合。例如 findPermutation([1, 2]) 应返回 [[1, 2], [2, 1]];[1, 2, 3] 则对应 6 种排列。
你的核心思路完全正确——采用「固定首元素 + 递归求解剩余元素全排列」的分治策略。但关键缺陷在于对递归结果的处理方式:原代码中 res.push([el, ...(temp)]) 使用了展开运算符却未遍历 temp 中的每一个子排列,导致 temp(本身是一个二维数组)被整体当作单个元素拼接,从而产生嵌套错误(如 [1, [[2,3],[3,2]]]),而非预期的 [[1,2,3], [1,3,2]]。
根本问题出在边界条件与数据结构一致性上:当 arr.length === 0 时,不应返回空数组 [],而应返回 [[]] —— 即包含一个空排列的数组。这是递归的基石:长度为 0 的数组有且仅有一种排列(空序列),它作为所有非空排列的“尾部”被拼接。若返回 [],则上层 for...of subPermutations 循环将无迭代项,导致整个分支丢失。
以下是修正后的完整实现:
function findPermutation(arr) {
function permute(arr) {
// ✅ 正确的基准情况:空数组对应一个空排列
if (arr.length === 0) return [[]];
const res = [];
for (const el of arr) {
// 过滤出除当前元素外的其余元素
const remaining = arr.filter(x => x !== el);
// 递归获取剩余元素的所有排列(每个都是数组)
const subPermutations = permute(remaining);
// ✅ 关键修复:遍历每一个子排列,分别前置当前元素
for (const subPerm of subPermutations) {
res.push([el, ...subPerm]);
}
}
return res;
}
return permute(arr);
}
console.log(findPermutation([1, 2, 3]));
// 输出: [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]
注意事项与优化提示:
-
去重假设:本实现默认输入数组元素互异。若含重复元素(如
[1, 1, 2]),需改用索引过滤或排序+剪枝避免重复排列; - 性能考量:时间复杂度为 O(n×n!),空间复杂度为 O(n)(递归栈深度),对大规模数组需谨慎使用;
-
替代方案:生产环境可考虑迭代法或使用标准库(如 JavaScript 暂无内置排列函数,但可通过
Array.from({length: n!}, ...)配合阶乘算法生成); -
调试技巧:在
permute([2,3])调用处添加console.log('sub:', subPermutations)可直观验证子问题返回值是否符合预期。
掌握这一模式不仅适用于全排列,也是理解回溯算法(如 N 皇后、组合总和)的起点:明确子问题定义、保证递归返回值结构一致、严谨处理每一层的组合逻辑,三者缺一不可。











