回溯合辑
LeetCode39
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
简单的回溯
基本就是回溯的模板,注意点就是返回的ArrayList不能作为参数放进方法中,因为每次回溯都会回到之前的状态。
直接上代码
class Solution {
List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
ArrayList<Integer> temp =new ArrayList<Integer>();
backstrace(candidates,target,temp,0);
return res;
}
public void backstrace(int[] candidates,int target,ArrayList<Integer> temp,int p){
if(target==0){
res.add(new ArrayList<>(temp));
return;
}
if(target<0)return;
for(int i=p;i<candidates.length;i++){
temp.add(candidates[i]);
backstrace(candidates,target-candidates[i],temp,i);
temp.remove(temp.size()-1);
}
}
}
LeetCode40
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
思路
主要的方法与上一题类似,这一题主要是如何除重的问题。除重的方法有两种:
- 使用Set天然的除重,但是代码结构会比较复杂
- 剪枝
注意到题目里说的是“每个数字在每个组合使用一次”,意味着数值相同但不是同一个的数字,可以在同一个组合中出现。即可以有[1,1,6]这种情况。不重复就需要按 顺序 搜索, 在搜索的过程中检测分支是否会出现重复结果 。注意:这里的顺序不仅仅指数组 candidates
有序,还指按照一定顺序搜索结果。
很容易想到的方案是:先对数组 升序 排序,重复的元素一定不是排好序以后相同的连续数组区域的第 1 个元素。也就是说,剪枝发生在:同一层数值相同的结点第 2、3 … 个结点,因为数值相同的第 1个结点已经搜索出了包含了这个数值的全部结果,同一层的其它结点,候选数的个数更少,搜索出的结果一定不会比第 1 个结点更多,并且是第 1 个结点的子集。
class Solution {
List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
ArrayList<Integer> temp =new ArrayList<Integer>();
Arrays.sort(candidates);
backstrace(candidates,target,temp,0);
return res;
}
public void backstrace(int[] candidates,int target,ArrayList<Integer> temp,int p){
if(target==0){
res.add(new ArrayList<>(temp));
return;
}
if(target<0)return;
for(int i=p;i<candidates.length;i++){
if(i>p&&candidates[i]==candidates[i-1])continue;//剪枝的位置
temp.add(candidates[i]);
backstrace(candidates,target-candidates[i],temp,i+1);
temp.remove(temp.size()-1);
}
}
}
总体的代码和上一题差不多,关键是在剪枝的位置
if(i>p&&candidates[i]==candidates[i-1])continue;
为什么这么写可以删除重复的。这里借用leetcode上看到的解释
这个避免重复当思想是在是太重要了。
这个方法最重要的作用是,可以让同一层级,不出现相同的元素。即
1
/ \
2 2 这种情况不会发生 但是却允许了不同层级之间的重复即:
/ \
5 5
例2
1
/
2 这种情况确是允许的
/
2
为何会有这种神奇的效果呢?
首先 cur-1 == cur 是用于判定当前元素是否和之前元素相同的语句。这个语句就能砍掉例1。
可是问题来了,如果把所有当前与之前一个元素相同的都砍掉,那么例二的情况也会消失。
因为当第二个2出现的时候,他就和前一个2相同了。
那么如何保留例2呢?
那么就用cur > begin 来避免这种情况,你发现例1中的两个2是处在同一个层级上的,
例2的两个2是处在不同层级上的。
在一个for循环中,所有被遍历到的数都是属于一个层级的。我们要让一个层级中,
必须出现且只出现一个2,那么就放过第一个出现重复的2,但不放过后面出现的2。
第一个出现的2的特点就是 cur == begin. 第二个出现的2 特点是cur > begin.
LeetCode46 全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
思路
基本的回溯,用一个标志数组用来除重。
class Solution {
public List<List<Integer>> permute(int[] nums) {
boolean label[]=new boolean[nums.length];
List<List<Integer>> res=new ArrayList<>();
backstrace(res,new ArrayList<Integer>(),nums,label);
return res;
}
public void backstrace(List<List<Integer>> res,ArrayList<Integer> temp,int nums[],boolean label[])
{
if(temp.size()==nums.length){
res.add(new ArrayList(temp));
return;
}
for(int i=0;i<nums.length;i++){
if(label[i]==true)continue;
else{
temp.add(nums[i]);
label[i]=true;
backstrace(res,temp,nums,label);
label[i]=false;
temp.remove(temp.size()-1);
}
}
}
}
LeetCode47 全排列2
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
思路
与上一题不同的地方在于给的数组中有重复元素,
- 用set去重,只需增加一步用set去重
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
boolean label[]=new boolean[nums.length];
List<List<Integer>> res=new ArrayList<>();
backstrace(res,new ArrayList<Integer>(),nums,label);
HashSet set=new HashSet(res);
//新增的步骤
res.clear();
res.addAll(set);
return res;
}
}
2.剪枝,思想与40一样,允许下一个,不允许自身
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
boolean label[]=new boolean[nums.length];
List<List<Integer>> res=new ArrayList<>();
Arrays.sort(nums);
backstrace(res,new ArrayList<Integer>(),nums,label);
return res;
}
public void backstrace(List<List<Integer>> res,ArrayList<Integer> temp,int nums[],boolean label[])
{
if(temp.size()==nums.length){
res.add(new ArrayList(temp));
return;
}
for(int i=0;i<nums.length;i++){
if(label[i]==true)continue;
if(i>0&&label[i-1]==false&&nums[i]==nums[i-1])continue;//去除同一树层的重复
temp.add(nums[i]);
label[i]=true;
backstrace(res,temp,nums,label);
label[i]=false;
temp.remove(temp.size()-1);
}
}
}
注意这里的
if(i>0&&label[i-1]==false&&nums[i]==nums[i-1])continue
label[i-1]==false的作用为判断是否在同一层,若在同一层则去除,为下一层则保留。