一、题目
1、全排列(LC 46)
2、子集(LC 78)
二、题解
1、全排列(LC 46)

(1)分析
使用回溯的方式解决。按排列的位置依次填充元素,并通过标记数组避免元素重复使用。定义用于存储单个排列结果的固定长度列表 path,存储所有排列结果的二维集合 ans,以及标记数组元素是否被使用的 onPath 数组。递归时考虑边界条件,当 i 等于数组长度时,说明已完成一个完整排列的构造,将当前排列存入结果集并终止当前递归。
在递归过程中,通过循环遍历所有数组元素,若元素未被标记使用,则将其放入当前排列的对应位置,标记为已使用后递归处理下一个位置;递归返回后,将元素的使用标记重置为未使用状态,完成回溯恢复现场,保证后续可以正常使用该元素,最终生成所有不重复的全排列。
时间复杂度:O(n * n!),其中 n 为 nums 的长度。
空间复杂度:O(n)。返回值的空间不计入。
(2)解答
java
class Solution {
public List<List<Integer>> permute(int[] nums) {
//初始化
int n = nums.length;
List<Integer> path = Arrays.asList(new Integer[n]); //存放每个排列的列表
List<List<Integer>> ans = new ArrayList<>(); //存放所有排列
boolean[] onPath = new boolean[n]; //标记数组中的元素是否可选
dfs(0, nums, ans, onPath, path);
return ans;
}
public void dfs(int i, int[] nums, List<List<Integer>> ans, boolean[] onPath ,List<Integer> path){
if( i == nums.length){ //边界条件,i超过了数组的最大下标
ans.add(new ArrayList<>(path)); //把这次所得的排列保存在结果中
return;
}
for(int j = 0; j < nums.length; j++){
if(!onPath[j]){
path.set(i,nums[j]); //当前元素可以选择时,放在列表中
onPath[j] = true; //标记这个元素已经被选择了
dfs(i + 1, nums, ans, onPath ,path); //考虑下一个元素
onPath[j] = false; //恢复现场,否则只能生成一个排列
}
}
}
}
2、子集(LC 78)

(1)分析
基于回溯算法的"选或不选"思想解决。对于数组中的每一个元素,都存在加入当前子集和不加入当前子集两种选择,长度为n的数组最终会生成2ⁿ个子集。
递归函数以 i 作为当前处理的数组元素下标,当 i 等于数组长度时,代表所有元素的选择决策已完成,将当前子集复制后存入结果集。递归过程分为两个分支:不选择当前元素时,直接递归处理下一个元素;选择当前元素时,将元素添加到子集后递归处理下一个元素,递归返回后移除最后添加的元素,完成回溯恢复现场,确保两个选择分支互不干扰,最终生成数组的全部子集。
时间复杂度:O(n * )
空间复杂度:空间复杂度:O(n)。返回值的空间不计入。
(2)解答
java
class Solution {
public List<List<Integer>> subsets(int[] nums) {
//初始化
List<List<Integer>> ans = new ArrayList<>(); //存放所有子集
List<Integer> path = new ArrayList<>(); //存放一个子集
dfs(0, nums, path, ans);
return ans;
}
public void dfs(int i, int[] nums, List<Integer> path, List<List<Integer>> ans){
if( i == nums.length){
ans.add(new ArrayList<>(path)); //边界条件,i超出数字下标
return;
}
//不选
dfs(i+1, nums, path, ans); //不选择当前元素,不加入子集中
//选
path.add(nums[i]); //选择当前元素,加入子集中
dfs(i+1, nums, path, ans);
path.removeLast(); //恢复现场
}
}