跟着labuladong刷回溯题的记录。
使用【回溯法】解决【子集、排列】问题:
分为【子集 元素无重复】 【子集 元素有重复】 【排列 元素无重复】 【排列 元素有重复】四类。
子集 无重
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
在回溯时传递i:
class Solution {
List<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<Integer>();
public List<List<Integer>> subsets(int[] nums) {
backtrac(nums, 0);
return res;
}
public void backtrac(int[] nums, int i){
//每个节点的值都是一个结果
res.add(new LinkedList<Integer>(path));
int n = nums.length;
for(int j = i; j < n; j++){
path.addLast(nums[j]);
backtrac(nums, j + 1);
path.removeLast();
}
}
}
子集 有重
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
先对元素排序,使相同的元素相邻,搜索时若当前元素和前一元素相同,剪枝当前元素
class Solution {
LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
backtrack(nums, 0);
return res;
}
public void backtrack(int[] nums, int i){
res.add(new LinkedList(path));
for(int j = i; j < nums.length; j++){
if(j > i && nums[j] == nums[j - 1]){//不是j>0,j>0就剪多了,j>0会导致j=i时也被剪
continue;
}
path.add(nums[j]);
backtrack(nums, j + 1);
path.removeLast();
}
}
}
排列 无重
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
排除使用过的元素,没有重复元素,只需要排除当前path中已经包含的元素
class Solution {
LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
backtrack(nums);
return res;
}
public void backtrack(int[] nums){
if(path.size() == nums.length){
res.add(new LinkedList<>(path));
return;
}
for(int i = 0; i < nums.length; i++){
//排序已经包含的元素
if(path.contains(nums[i])){
continue;
}
//做选择
path.add(nums[i]);
backtrack(nums);
//撤销选择
path.removeLast();
}
}
}
排列 有重
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
先对元素排序,使相同元素相邻。
因为存在相同元素,直接全排列会搜索到[1, 1’, 2]和[1’, 1, 2]的相同结果,去重的方式为:保证相同元素在排列结果中的相对顺序。也就是:当我们搜到第1’时,必须保证1已经使用过,因此若前一相同元素未被使用,剪枝。
class Solution {
LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> res = new LinkedList<>();
boolean[] used;
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
int n = nums.length;
used = new boolean[n];
backtrack(nums);
return res;
}
public void backtrack(int[] nums){
int n = nums.length;
if(path.size() == n){
res.add(new LinkedList<>(path));
return;
}
for(int j = 0; j < n; j++){
if(used[j]){
continue;
}
if(j > 0 && nums[j] == nums[j - 1] && !used[j - 1]){
continue;
}
path.add(nums[j]);
used[j] = true;
backtrack(nums);
path.removeLast();
used[j] = false;
}
}
}
总结
子集
只能选择当前位置后面的元素,通过在回溯时传递搜索的起始索引实现。
若元素有重,先排序,搜索到重复元素(当前元素和前一元素相同)时,剪枝。
排列
不能选择用过的数字,通过判断path中是否存在或者使用used数组记录。
若元素有重,先排序,需要保证相同元素在结果中的相对顺序是一致的,因此:搜索到重复元素(当前元素和前一元素相同)且 前一元素未被使用 时,剪枝。