
一、核心思路
-
题目给定不含重复数字的数组,需要找出全部排列组合,适合回溯 DFS。
-
使用
path记录当前正在构造的一条排列;used布尔数组标记下标对应的数字是否已经被选入 path,避免重复选取。 -
步骤:
- 终止条件:当前
path的长度等于原数组长度,代表找到一组完整排列,复制存入结果集。 - 遍历每一个数字:如果该数字已经被使用,则跳过。
- 做选择:标记数字已使用,将数字加入 path。
- 递归:进入下一层,继续挑选剩下的数字。
- 回溯(撤销选择):递归返回后,移除 path 末尾数字,取消 used 标记,尝试其他分支。
- 终止条件:当前
二、完整 Java 代码
ini
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used = new boolean[nums.length];
backtrack(nums, used, path, res);
return res;
}
private void backtrack(int[] nums, boolean[] used, List<Integer> path, List<List<Integer>> res){
if(path.size() == nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i = 0; i < nums.length; i++){
if(used[i]) continue;
used[i] = true;
path.add(nums[i]);
backtrack(nums, used, path, res);
path.remove(path.size()-1);
used[i] = false;
}
}
}
三、实例演示(nums = 1,2,3)
| 步骤 | path(当前排列) | used 数组 | 操作说明 |
|---|---|---|---|
| 1 | \[\] | F,F,F | i=0,未使用;used 0=true,path=1,递归 |
| 2 | 1 | T,F,F | i=0 跳过;i=1 未使用;used 1=true,path=1,2,递归 |
| 3 | 1,2 | T,T,F | i=0、1 跳过;i=2 未使用;used 2=true,path=1,2,3 |
| 4 | 1,2,3 | T,T,T | path 长度等于 3,存入 res;return 回溯 |
| 5 | 1,2 | T,T,F | 撤销:删掉 3,used 2=false;for 循环结束,回溯 |
| 6 | 1 | T,F,F | 撤销:删掉 2,used 1=false;i=2 未使用,path=1,3,递归 |
| 7 | 1,3 | T,F,T | i=1 未使用,path=1,3,2,存入 res,回溯 |
| 8 | ...... | ...... | 持续回溯,依次探索 2,1,3、2,3,1、3,1,2、3,2,1 |
四、复杂度分析
- 时间复杂度:(O(n * n!)),总共有 (n!) 种排列,每种排列复制数组花费(O(n))
- 空间复杂度:(O(n)),递归栈、path 集合、used 标记数组,不计返回结果占用空间
五、易错点总结
- 保存结果必须写
res.add(new ArrayList<>(path)),不能直接 add (path),path 是同一个引用,后续回溯会修改内容。 - 回溯顺序固定:先递归,递归返回之后,再撤销选择(删元素、改 used) 。
- used 数组是按下标标记,不是按数值标记,适合本题无重复数字场景。
六、感悟
回溯本质就是暴力搜索所有可行方案,核心是「做选择 → 递归深入 → 撤销选择」,撤销选择就是回溯,保证不同搜索分支之间互不干扰。