46. 全排列 
题目描述
给定一个不含重复数字的数组 nums,返回其所有可能的全排列。
题型判断
每个位置都要从“尚未使用的数字”中选择一个,选择后继续处理下一个位置;走到底后撤销选择并尝试其他分支。这是典型回溯。
全排列与组合、子集问题的一个重要区别是:每一层都可以从整个数组中选择元素,只要该元素没有出现在当前路径中。因此循环每次都从索引 0 开始,不能使用 startIndex 限制选择范围。
例如生成 [2,1,3] 时,选择 2 之后,下一层仍然需要回头选择索引更小的 1。如果只允许向后选择,就会漏掉这个排列。
核心思路
递归过程中维护:
path:当前已经选择的排列前缀。used[i]:nums[i]是否已经出现在当前路径中。- 当
path.length === nums.length时,保存路径副本。
在每次进入递归时,都应满足以下状态关系:
所以一次完整选择必须包含“做选择、继续递归、撤销选择”三个步骤:
以 nums = [1,2,3] 为例,递归树如下:
每深入一层,就确定排列中的一个位置;当路径长度等于 nums.length 时,得到一个完整排列。
代码实现
复杂度与易错点
- 时间复杂度:
O(n × n!),共有n!个排列,复制每个排列需要O(n)。 - 辅助空间复杂度:
O(n),路径、标记数组和递归深度均为线性。 - 返回结果占用
O(n × n!)空间,共有n!个长度为n的排列。 - 保存结果时必须复制
path,否则所有结果会引用同一个数组。 - 回溯返回后必须同时撤销
path和used状态。 - 每层循环必须从
0开始;startIndex适用于组合、子集等只向后选择的问题,不适用于全排列。
如果数组包含重复元素
本题保证数组元素互不相同,所以只需要通过 used[i] 防止同一个元素在一条路径中被重复选择。
如果数组允许包含重复元素,仅使用 used 仍然会生成重复排列。此时需要先排序,再增加同层去重:
两个判断的职责不同:
used[i]:防止同一个下标的元素在当前路径中被重复选择。nums[i] === nums[i - 1] && !used[i - 1]:跳过同一层中值相同的重复选择,避免生成重复排列。

