leetcode刷题思路-----回溯
对于回溯问题的主要思路在于找出回溯路径,最经典的就是排列和组合的问题。
主要模板
private void backtrace1(int[] nums){
//何时取出何时的结果
if("符合条件"){
res.add("将这次结果加入解集");
return;
}
//在可选的集合中循环来取,加入当前路径
for(int i=0; i< nums.length; i++){
//去重等特判,直接跳过
if(){
continue;
}
//加入当前路径
list.add(nums[i]);
//向下继续探索
backtrace1(nums);
//回退状态
list.remove(list.size()-1);
}
}
1. 全排列1与全排列2
/*
1.
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
2.
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
* */
private void backtrace1(int[] nums){
//只要加入所有的数字就可记录下来
if(list.size()==nums.length){
res.add(new ArrayList(list));
return;
}
//在数组里取数字
for(int i=0; i< nums.length; i++){
//已经取过的直接跳过
if(list.contains(nums[i])){
continue;
}
//加入当前数
list.add(nums[i]);
backtrace1(nums);
//回退
list.remove(list.size()-1);
}
}
//对于有重复数字的排列,要对其进行剪枝
//例如nums = {1,1*,2}
private void backtrace2(int[] nums){
if(list1.size()==nums.length){
res1.add(new ArrayList(list1));
return;
}
for(int i=0; i<nums.length; i++){
if(used[i]){
continue;
}
//对于相同的数字,其路径会重复,那么对于前一个相同的数字未访问,那么这次路径就会重复
//例如存在1,1*,当1*继续往下走时,必将出现 1*->1的路,而这个是重复的,需要剪枝
if(i>0&&nums[i]==nums[i-1]&&!used[i-1]){
continue;
}
list1.add(nums[i]);
//记录访问过的节点
used[i] = true;
backtrace2(nums);
list1.remove(list1.size()-1);
//回退
used[i] = false;
}
}
总结:
1.对于排列,不重复的数组正常做,每次for内的递归从头(i=0)开始,自己路径内去重。
2.对于存在重复的数组,需要剪枝,按照相同元素,对第二个(1*)剪枝。剪枝的依据是加入一个used数组。
3.切记对于相同数字去重,要对数组排序!
2.组合总和1与组合总和2
/*
1.
给你一个 无重复元素 的整数数组candidates 和一个目标整数target,找出candidates中可以使数字和为目标数target的不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
链接:https://leetcode-cn.com/problems/combination-sum
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
2.给定一个候选人编号的集合candidates和一个目标数target,找出candidates中所有可以使数字和为target的组合。
candidates中的每个数字在每个组合中只能使用一次。注意:解集不能包含重复的组合。
链接:https://leetcode-cn.com/problems/combination-sum-ii
* */
//1.不重复
private void backtrace_zuhe(int[] nums,int cur,int target){
if(target==0){
res.add(new ArrayList(list));
return;
}
for(int i=cur; i<nums.length&&target>=nums[i]; i++){
list.add(nums[i]);
target -= nums[i];
//区别就在于组合是没有顺序的,因此要下次遍历时不能走回头路,从i继续走
backtrace_zuhe(nums, i, target);
target += nums[i];
list.remove(list.size()-1);
}
}
//2.重复的
private void backtrace_zuhe1(int[] nums,int cur,int target){
if(target==0){
res1.add(new ArrayList(list1));
return;
}
for(int i=cur; i<nums.length&&target>=nums[i]; i++){
//和排列重复的一样加入剪枝
if(i>0&&nums[i]==nums[i-1]&&!used[i-1]){
continue;
}
list1.add(nums[i]);
target -= nums[i];
used[i] = true;
backtrace_zuhe1(nums, i+1, target);
target += nums[i];
used[i] = false;
list1.remove(list1.size()-1);
}
}
总结:
1.组合内部是无序的,因此递归时注意不要走回头路。
2.同样的,重复数组记得排序。
3.可以在for中提前剪枝来提高效率。
这就时回溯的基本套路,日后可以更新更多抽象的或者具体数据结构的回溯,例如二叉树或者二维数组,dan本质都是这个思路。