算法 - 全排列(Permutations)
1. 题目
给定一个没有重复数字的数组 nums,返回其所有可能的全排列。可以按任意顺序返回答案。
示例:
1 | 输入: nums = [1,2,3] |
2. 解题思路
排列与组合/子集的最大区别:顺序有关,每层都要从头开始选。
2.1 回溯 + used 标记
- 每一层递归都遍历整个数组,用
used[i]记录nums[i]是否已在当前路径中。 - 选中一个数就
path.push+used[i] = true,递归进入下一层;返回后撤销(pop+used[i] = false)。 - 当
path.length === nums.length时到达叶子节点,收集一个完整排列。
排列数 n!,故:
- 时间复杂度:O(n·n!),空间复杂度:O(n)。
2.2 交换法(原地生成,省空间)
在 index 位置,依次把后面的元素换到前面,递归处理 index+1。优点是不需要额外 used 数组,但顺序不保证字典序。
3. TypeScript 实现
3.1 回溯 + used
1 | function permute(nums: number[]): number[][] { |
3.2 交换法(原地)
1 | function permuteSwap(nums: number[]): number[][] { |
4. 面试延伸
- 变体:Permutations II(含重复数字):先排序,同层去重
if (i > 0 && nums[i] === nums[i-1] && !used[i-1]) continue;——前一个相同值未被使用时跳过,保证相同元素只按固定相对顺序出现一次。 - 子集 vs 组合 vs 排列 的递归起点:子集/组合用
start(不回头),排列每层从 0 开始(靠used去重)。这是最容易混的考点。 - JS 中
path.push(...)传引用会污染结果,务必[...path]或path.slice()拷贝。
难度:中等 | LeetCode 46 | 回溯法核心题