0
点赞
收藏
分享

微信扫一扫

【代码随想录】回溯算法专栏(java版本)

雨鸣静声 2022-04-08 阅读 52

目录

前言

总结回溯算法的题型
按照规模将其题型都熟练掌握

学习的链接大框架为:
回溯算法理论基础

  • 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);
        }
    }
}
举报

相关推荐

0 条评论