目录
前言
总结回溯算法的题型
 按照规模将其题型都熟练掌握
学习的链接大框架为:
 回溯算法理论基础
LinkedList<Integer> sonlist=new LinkedList<>();
双向链表可以删除最前或者添加最前的节点,删除的时候通过removeLast();List<Integer> sonlist=new ArrayList<>();
单链表只能添加,或者删除,删除的时候通过remove(下标值)
组合
77. 组合(中等)
题目:
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
示例 2:
提示:
1 <= n <= 20
 1 <= k <= n
思路:
class Solution {
    
    List<List<Integer>> list =new ArrayList<List<Integer>>();
    LinkedList<Integer> sonlist=new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {
    	if (k <= 0 || n < k) {
            return res;
        }   
        backtrace(n,k,1);
        return list;
    }
    public void backtrace(int n,int k,int startindex){
        if(sonlist.size()==k){
            list.add(new ArrayList<>(sonlist));
            return;
        }
        for(int i=startindex;i<=n;i++){
            sonlist.addLast(i);
            backtrace(n,k,i+1);
            //不用带下标
            sonlist.removeLast();
        }
    }
}
 
也可以将以上的双向列表换为如下:Deque<Integer> sonlist=new ArrayDeque<>();
 进一步的优化,将其for条件的循环更改为:
for(int i=startindex;i<=n-(k-sonlist.size())+1;i++){
 
这是因为如果 n = 7, k = 4,从 5 开始搜索就已经没有意义了
优化过程:
 已经选择的元素个数:sonlist.size();
还需要的元素个数为: k - sonlist.size();
在集合n中至多要从该起始位置 : n - (k - sonlist.size()) + 1,开始遍历
为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。
而且有个错误点:
for(int i=startindex;i<=n-(k-sonlist.size())+1;i++){
            sonlist.add(i);
            backtrace(n,k,i+1);
            sonlist.remove(sonlist.size()-1);
        }
 
内部结构不能使用list列表,即使删除节点也会删除错误,必须要用临时变量记住就会比较麻烦
 
216. 组合总和 III(中等)
题目:
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
只使用数字1到9
 每个数字 最多使用一次
 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例 1:
示例 2:
输入: k = 3, n = 9
 输出: [[1,2,6], [1,3,5], [2,3,4]]
 解释:
 1 + 2 + 6 = 9
 1 + 3 + 5 = 9
 2 + 3 + 4 = 9
 没有其他符合的组合了。
 示例 3:
输入: k = 4, n = 1
 输出: []
 解释: 不存在有效的组合。
 在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。
提示:
2 <= k <= 9
 1 <= n <= 60
思路:
计算其sum值,可以将其sum值也遍历,不用额外定义一个int sum在函数外。
 注意其终止条件
class Solution {
    List<List<Integer>> list=new ArrayList<>();
    LinkedList<Integer> sonlist =new LinkedList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        backtrace(k,n,1,0);
        return list;
    }
    public void backtrace(int k,int n,int startindex,int sum){
        if(sum>n)return;
        
        if(sonlist.size()==k){
            if(sum==n)list.add(new ArrayList<>(sonlist));
            return;
        }
        for(int i=startindex;i<=9-(k-sonlist.size())+1;i++){
            sonlist.addLast(i);
            sum=sum+i;
            backtrace(k,n,i+1,sum);
            sonlist.removeLast();
            sum=sum-i;
        }
    }
}
 
39. 组合总和(中等)
题目:
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例 1:
示例 2:
示例 3:
提示:
1 <= candidates.length <= 30
 1 <= candidates[i] <= 200
 candidate 中的每个元素都 互不相同
 1 <= target <= 500
思路:
这题的思路主要跟上面的区别,在于数组是可以重复使用。
class Solution {
    List<List<Integer>> list=new ArrayList<List<Integer>>();
    //删除回溯使用双端队列比较好
    LinkedList<Integer> sonlist=new LinkedList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates); // 先进行排序
        backtrace(candidates,target,0,0);
        return list;
    }
    public void backtrace(int []candidates,int target,int sum,int startindex){
        //如果sum大于target 优先返回
        if(sum>target)return ;
        //记得转换类型
        if(sum==target){
            list.add(new ArrayList<>(sonlist));
            return;
        }
        
        //从startindex开始遍历,而且条件是小于数组长
        for(int i=startindex;i<candidates.length;i++){
            sonlist.addLast(candidates[i]);
            sum=sum+candidates[i];
            //这里的数组值是可以重复的,所以startindex是可以为i本身自已。
            backtrace(candidates,target,sum,i);
            //移除最新的节点,也要移除sum中那个数组值
            sonlist.removeLast();
            sum=sum-candidates[i];
        }
    }
}
 
40. 组合总和 II(中等)
题目:
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
示例 1:
示例 2:
提示:
1 <= candidates.length <= 100
 1 <= candidates[i] <= 50
 1 <= target <= 30
思路:
这道题跟上面的思路是大同小异,差不多,区别在于不可重复使用,但是数组中有值一样,遍历出来的列表,可能会有重复的,类似这种.
 
 解决上面的问题:把所有组合求出来,再用set或者map去重,这么做很容易超时!
 所以要在搜索的过程中就去掉重复组合。
可以通过外加这一行代码,具体如下所示:
 只要大于下标值,而且值和之前那个相等,既跳过该下标即可
if(i>startindex&&candidates[i]==candidates[i-1])continue;
 
完整代码如下所示:
class Solution {
    List<List<Integer>> list =new ArrayList<List<Integer>>();
    LinkedList<Integer> sonlist=new LinkedList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backtrace(candidates,target,0,0);
        return list;
    }
    public void backtrace(int []candidates,int target,int sum,int startindex){
        if(sum>target)return;
        if(sum==target){
            list.add(new ArrayList<>(sonlist));
            return ;
        }
        for(int i=startindex;i<candidates.length;i++){
            if(i>startindex&&candidates[i]==candidates[i-1])continue;
            sonlist.addLast(candidates[i]);
            sum=sum+candidates[i];
            backtrace(candidates,target,sum,i+1);
            
            sonlist.removeLast();
            sum=sum-candidates[i];
        }
    }
}
 
也可以通过一维的标记数组,标记其已经使用过该数字了
class Solution {
    List<List<Integer>> list =new ArrayList<List<Integer>>();
    LinkedList<Integer> sonlist=new LinkedList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        //定义一个一维标记数组
        boolean [] flag=new boolean [candidates.length];
        backtrace(candidates,target,0,0,flag);
        return list;
    }
    public void backtrace(int []candidates,int target,int sum,int startindex,boolean [] flag){
        if(sum>target)return;
        if(sum==target){
            list.add(new ArrayList<>(sonlist));
            return ;
        }
        for(int i=startindex;i<candidates.length;i++){
            //比较的是前一个数组,数组的boolean也是上一个值
            if(i>0&&candidates[i]==candidates[i-1]&&flag[i-1]==false)continue;
            sonlist.addLast(candidates[i]);
            sum=sum+candidates[i];
            //一开始初始化为true
            flag[i]=true;
            backtrace(candidates,target,sum,i+1,flag);
            //回溯之后变为false,标记数组
            flag[i]=false;
            sonlist.removeLast();
            sum=sum-candidates[i];
        }
    }
}
 
可配合如下进行理解:
 (关于该图来源于 代码随想录的网址)
 
子集
78. 子集(中等)
题目:
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
示例 2:
提示:
1 <= nums.length <= 10
 -10 <= nums[i] <= 10
 nums 中的所有元素 互不相同
思路:
class Solution {
    List<List<Integer>> list=new ArrayList<List<Integer>>();
    LinkedList<Integer> sonlist=new LinkedList<>();
    public List<List<Integer>> subsets(int[] nums) {
        backtrace(nums,0);
        return list;
    }
    public void backtrace(int []nums,int startindex){
        //「遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合」。
        list.add(new ArrayList<>(sonlist));
        //终止条件本身可不加
       if(sonlist.size()>=nums.length)return ;
       for(int i=startindex;i<nums.length;i++){
           sonlist.addLast(nums[i]);
           backtrace(nums,i+1);
           sonlist.removeLast();
         
       } 
    }
}
 
90. 子集 II(中等)
题目:
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
示例 2:
提示:
1 <= nums.length <= 10
 -10 <= nums[i] <= 10
思路:
 添加标记数组:
class Solution {
    List<List<Integer>> list =new ArrayList<List<Integer>>();
    LinkedList<Integer> sonlist=new LinkedList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        //先排好顺序
        Arrays.sort(nums);
        boolean []flag=new boolean[nums.length];
        backtrace(nums,0,flag);
        return list;
    }
    public void backtrace(int []nums,int startindex,boolean [] flag){
        //该算法为找子集,每一次遍历都可以输出添加
        list.add(new ArrayList<>(sonlist));
        //开始的下标值如果大于字段长,就返回
        if(startindex>=nums.length)return;
        for(int i=startindex;i<nums.length;i++){
            //通过大于下标以及如果两者相等,跳过该循环
            //切记flag的下标也是i-1,而不是i,重点记忆
            if(i>0&&nums[i]==nums[i-1]&&flag[i-1]==false)continue;
            sonlist.addLast(nums[i]);
            flag[i]=true;
            backtrace(nums,i+1,flag);
            flag[i]=false;
            sonlist.removeLast();
        }
    }
}
 
不添加标记数组的方式:
class Solution {
    List<List<Integer>> list =new ArrayList<List<Integer>>();
    LinkedList<Integer> sonlist=new LinkedList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        //先排好顺序
        Arrays.sort(nums);
        backtrace(nums,0);
        return list;
    }
    public void backtrace(int []nums,int startindex){
        //该算法为找子集,每一次遍历都可以输出添加
        list.add(new ArrayList<>(sonlist));
        //开始的下标值如果大于字段长,就返回
        if(startindex>=nums.length)return;
        for(int i=startindex;i<nums.length;i++){
            //通过大于下标以及如果两者相等,跳过该循环
            if(i>startindex&&nums[i]==nums[i-1])continue;
            sonlist.addLast(nums[i]);
            backtrace(nums,i+1);
            sonlist.removeLast();
        }
    }
}
 
排列
46. 全排列(中等)(不包含重复数字)
题目:
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
示例 2:
示例 3:
提示:
1 <= nums.length <= 6
 -10 <= nums[i] <= 10
 nums 中的所有整数 互不相同
思路:
这里和77.组合问题 、131.切割问题和78.子集问题最大的不同就是for循环里不用startIndex了
因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。
排列问题总结
 每层节点都是从0开始搜索而不是startindex
 需要使用uded数组记录path放了哪些元素

class Solution {
    List<List<Integer>> list=new ArrayList<List<Integer>>();
    LinkedList<Integer> sonlist=new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
        boolean [] flag=new boolean[nums.length];
        backtrace(nums,flag);
        return list;
    }
    public void backtrace(int [] nums,boolean [] flag){
        if(sonlist.size()==nums.length ){
            list.add(new ArrayList<>(sonlist));
            return ;
        }
        //每一层回溯遍历,都是从0开始,判断是否为true
        // 1为true,在回溯的时候,1还是为true,则跳过1,来到了2。遍历完1 2 3类似这样子后。
        for(int i=0;i<nums.length;i++){
            //如果这一层用了true的节点,则跳回
            if(flag[i]==true)continue;
            sonlist.addLast(nums[i]);
            flag[i]=true;
            backtrace(nums,flag);
            flag[i]=false;
            sonlist.removeLast();
        }
    }
}
 
47. 全排列 II(中等)
题目:
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
示例 2:
提示:
1 <= nums.length <= 8
 -10 <= nums[i] <= 10
思路:
class Solution {
    List<List<Integer>> list=new ArrayList<List<Integer>>();
    LinkedList<Integer> sonlist=new LinkedList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean [] flag=new boolean[nums.length];
        Arrays.sort(nums);
        backtrace(nums,flag,0);
        return list;
    }
    public void backtrace(int []nums,boolean [] flag,int startindex){
        if(sonlist.size()==nums.length){
            list.add(new ArrayList<>(sonlist));
            return ;
        }
        for(int i=0;i<nums.length;i++){
            if(i>0&&nums[i]==nums[i-1]&&flag[i-1]==false)continue;
            if(flag[i]==true)continue;
            sonlist.addLast(nums[i]);
            flag[i]=true;
            backtrace(nums,flag,startindex+1);
            flag[i]=false;
            sonlist.removeLast();
        }
    }
}
 
其他
491. 递增子序列(中等)
题目:
给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
示例 1:
示例 2:
提示:
1 <= nums.length <= 15
 -100 <= nums[i] <= 100
思路:
区别于上面其他思路:
 因为原数组中不像之前的都是排序后在进行查重,所以不可使用之前的方法
 在搜索遍历的时候就进行查重 而且 确定好排序

 而且不可用上面思路中的标记数组
 
 需要使用类似哈希表中的数组计数
注意题目中说了,数值范围[-100,100],所以完全可以用数组来做哈希
class Solution {
    List<List<Integer>> list =new ArrayList<List<Integer>>();
    List<Integer> sonlist=new ArrayList<>();
    boolean [] flag;
    public List<List<Integer>> findSubsequences(int[] nums) {
        flag=new boolean [nums.length];
        backtrace(nums,0);
        return list;
    }
    public void backtrace(int [] nums,int startindex){
        //确定列表的尺寸大于等于2才添加进去
        if(sonlist.size()>=2){
            list.add(new ArrayList<>(sonlist));
        }
        
        if(startindex>nums.length)return ;
        int[] used = new int[201];
        
        for(int i=startindex;i<nums.length;i++){
            //因为原数组中不像之前的都是排序后在进行查重,所以不可使用之前的方法
            //在搜索遍历的时候就进行查重 而且 确定好排序 
            if(!sonlist.isEmpty()&&nums[i]<sonlist.get(sonlist.size()-1) ||used[nums[i] + 100] == 1 )continue;
            used[nums[i] + 100] = 1;
            flag[i]=true;
            sonlist.add(nums[i]);
            backtrace(nums,i+1);
            sonlist.remove(sonlist.size() - 1);
        }
    }
}









