回溯算法问题汇总

模版

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

在这里插入图片描述

一. 组合问题

回溯法,一般可以解决如下几种问题:

组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等

组合是不强调元素顺序的,排列是强调元素顺序。

例如:{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合了。

记住组合无序,排列有序,就可以了

77. 组合

在这里插入图片描述

class Solution {
    public List<List<Integer>> combine(int n, int k) {
        if(n<k){
            return lists;
        }

        find(n,1,k);
        return lists;
    }

    private void find(int n, int begin,int k){
        if(k == 0){
            lists.add(new ArrayList<>(list));
            return;
        }
        if(begin > n){
            return;
        }
        for(int i=begin;i<=n;i++){
            //如果后面的数不够了,就不用考虑了
            if(n-i+1 < k){
                return;
            }

            // 将当前数放入list
            list.add(i);
            k--;
            find(n,i+1,k);
            k++;
            list.remove(list.size()-1);
        }
    }


    private List<List<Integer>> lists = new ArrayList<List<Integer>>();
    private List<Integer> list = new ArrayList<>();
}

做了简单的剪枝,如果剩余的元素加上已选中的元素,不够凑成k个,那就直接返回。

216.组合总和III

在这里插入图片描述

class Solution {
    public List<List<Integer>> combinationSum3(int k, int n) {
        find(1,k,n);
        return list;
    }

    private void find(int begin,int k, int sub){
        //如果减成了负数,直接返回
        if(sub<0){
            return;
        }
        if(sub==0 && path.size() == k){
            list.add(new ArrayList<>(path));
            return;
        }

        for(int i=begin;i<10;i++){
            // 直接减
            path.add(i);
            sub -= i;
            find(i+1,k,sub);
            sub += i;
            path.remove(path.size()-1);
        }
    }

    private List<List<Integer>> list = new ArrayList<List<Integer>>();
    private List<Integer> path = new ArrayList<Integer>();
}

17.电话号码的字母组合

在这里插入图片描述

class Solution {
    public List<String> letterCombinations(String digits) {
        if(digits == "" || digits == null || digits.length() == 0){
            return new ArrayList<String>();
        }
        
        find(0,digits);
        return list;

    }

    private void find(int index,String digits ){
        if(index == digits.length()){
            list.add(path.toString());
            return;
        }

        String str = map[Integer.parseInt(String.valueOf(digits.charAt(index)))];

        for(int i=0;i<str.length();i++){
            path.append(str.charAt(i));
            find(index+1,digits);
            path.deleteCharAt(path.length()-1);
        }
    }

    // 预先放入数组中,然后根据字符串数字-数组序列号取出
    private String[] map =  new String[]{
    "", // 0
    "", // 1
    "abc", // 2
    "def", // 3
    "ghi", // 4
    "jkl", // 5
    "mno", // 6
    "pqrs", // 7
    "tuv", // 8
    "wxyz", // 9
    };

    private List<String> list = new ArrayList<>();
    private StringBuilder path = new StringBuilder();
}

39. 组合总和

在这里插入图片描述

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        find(0,candidates,target);
        return lists;
    }

    private void find(int begin,int[] candidates,int target){
        if(target == 0){
            lists.add(new ArrayList<>(path));
            return; 
        }
        if(target<0 || begin >= candidates.length){
            return;
        }

        for(int i=begin;i<candidates.length;i++){
            // 剪枝,如果我已经比它小了,后面更大的数你就不用考虑了
            if(candidates[i] > target){
                return;
            }
            target -= candidates[i];
            path.add(candidates[i]);
            // 由于可以重复取数,所以没有i+1
            find(i,candidates,target);
            path.remove(path.size()-1);
            target += candidates[i];
        }
    }

    private List<List<Integer>> lists = new ArrayList<List<Integer>>();
    private List<Integer> path = new ArrayList<Integer>();
}

40.组合总和II

在这里插入图片描述

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        used = new int[candidates.length];
        find(0,candidates,target);
        return lists;
    }

    private void find(int begin, int[] candidates,int target){
        // 如果减完了,结束
        if(target == 0){
            lists.add(new ArrayList(path));
            return;
        }

         // 如果搜完了或者已经成了负数,结束
        if(target<0 || begin >= candidates.length){
            return;
        }


        for(int i=begin;i<candidates.length;i++){
            // 如果是重复的数字,同一层的搜过了就别搜我了,谢谢
            // 比如 2 2 ,如果是同一层的搜过了那它就used=0,但如果是递归下来的,就是used=1
            // 注意continue和return 这个for循环相同于横向的一个遍历,如果到这一步横向的后面节点不用了就return,如果只是跳过这个节点继续去执行横向的下个节点,则用continue
            if(i>0 && candidates[i] == candidates[i-1] && used[i-1]==0){
                continue;
            }

            // 如果我已经比target大了,那就别考虑我了,我后面更大的也别考虑(已排序)
            if(candidates[i] > target){
                return;
            }
            // 如果我被用过了,千万别用我
            if(used[i] == 1){
                continue;
            }

            target-=candidates[i];
            used[i]=1;
            path.add(candidates[i]);
            find(i+1,candidates,target);
            used[i]=0;
            target+=candidates[i];
            path.remove(path.size()-1);
        }
    }

    private List<List<Integer>> lists = new ArrayList<List<Integer>>();
    private List<Integer> path = new ArrayList<Integer>();
    private int[] used;
}

在这里插入图片描述

131.分割回文串

在这里插入图片描述

class Solution {
    public List<List<String>> partition(String s) {
        if(s == null || s.length() == 0){
            return lists;
        }
        find(0,s);
        return lists;
    }

    private void find(int begin, String s){

        // 如果切割到底了,且都是回文,则成功
        if(begin == s.length()){
            lists.add(new ArrayList<>(path));
            return;
        }

        for(int i=begin;i<s.length();i++){
            // 切割i下标
            // 如果非回文,直接跳过,去截取后面的吧
            if(!isReverse(s,begin,i)){
                continue;
            }
            path.add(s.substring(begin,i+1));
            find(i+1,s);
            path.remove(path.size()-1);
        }
    }

    // 判断是否是回文
    private boolean isReverse(String s,int begin, int end) {
        for (int i = begin, j = end; i < j; i++, j--) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
        }
        return true;
    }


    private List<List<String>> lists = new ArrayList<List<String>>();
    private List<String> path = new ArrayList<String>();
    
}

在这里插入图片描述

93.复原IP地址

在这里插入图片描述

class Solution {
      public List<String> restoreIpAddresses(String s) {
        //如果字符串长度小于4或大于12,则不符合
        if(s == null || s.length() < 4 || s.length() > 12){
            return list;
        }
        find(0,s);
        return list;

    }

    private void find(int begin,String s){
        // 如果剩余的字符串长度不能满足剩下的ip分配,或者太多了,则结束
        if((s.length()-begin) < (4-path.size()) || (s.length()-begin) > (4-path.size()) * 3){
            return;
        }



        // 如果list长度为3,说明是最后一个了,可以加进去了
        if(path.size()==3){
            String str = s.substring(begin);
            if(!isVaild(str)){
                return;
            }
           
            path.add(str);
            list.add(pathToList(path));
            path.remove(path.size()-1);
            return;
        }

        // 循环切割
        for(int i=begin;i<s.length() && i<=begin+2;i++){

            String str = s.substring(begin,i+1);
            // 如果不满足大小,结束
            if(!isVaild(str)){
                continue;
            }

            path.add(str);
            find(i+1,s);
            path.remove(path.size()-1);
        }

    }

    // 判断切割的字符串是否合法
    private boolean isVaild(String str){
        // 不可用是以0开头的多位数
        if(str.charAt(0) == '0' && str.length() > 1){
                return false;
        }
        // 判断该数字是否不大于255
        return Integer.parseInt(str) <= 255;
    }
    

    // 将path转化为地址ip字符串
    private String pathToList(List<String> path){
        StringBuilder str = new StringBuilder();
        path.forEach(p -> {
            str.append(p).append(".");
        });
        str.deleteCharAt(str.length()-1);
        return str.toString();
    }

    private List<String> list = new ArrayList<String>();
    private List<String> path = new ArrayList<String>();
    private StringBuilder str = new StringBuilder();

}

78.子集

在这里插入图片描述

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
       find(0,nums);
       return list;
    }

    public void find(int begin, int[] nums){
    // 一进来就直接加入,这个的区别就是所有的树节点全部算上
        list.add(new ArrayList<>(path));

       for(int i=begin;i<nums.length;i++){
           path.add(nums[i]);
           find(i+1,nums);
           path.remove(path.size()-1);
       }
    }

    private List<List<Integer>> list = new ArrayList<List<Integer>>();
    private List<Integer> path = new ArrayList<Integer>(); 
}

在这里插入图片描述
之前的题目都是最后的叶子结点就是答案,子集问题是把所有的结点全部算上才行。

90.子集II

在这里插入图片描述

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        used = new int[nums.length];
        find(0,nums);
        return list;
    }


    // 递归枚举nums[0]、nums[1]、、、nums[n-1]这n个数选或不选
    public void find(int begin, int[] nums){
        list.add(new ArrayList<>(path));
        
        for(int i=begin;i<nums.length;i++){
            if(i>0&&nums[i]==nums[i-1]&&used[i-1]==0){
                continue;
            }
            path.add(nums[i]);
            used[i]=1;
            find(i+1, nums);
            used[i]=0;
            path.remove(path.size() - 1);
        }
    }
    private List<List<Integer>> list = new ArrayList<List<Integer>>();
    private List<Integer> path = new ArrayList<Integer>(); 
    int[] used;
}

491.递增子序列

在这里插入图片描述

class Solution {
    public List<List<Integer>> findSubsequences(int[] nums) {
        find(0,nums);
        return list;
    }

    private void find(int begin, int[] nums){

        // 如果当前数大于上一个数,则直接加入path,取的树上所有节点
        if(path.size() >= 2){
            list.add(new ArrayList<>(path));
        }
        // 局部变量,只用于该层的遍历
        int[] used = new int[201];
        for(int i=begin;i<nums.length;i++){
            //在该层的遍历中,相同元素不需要重复再执行
            if(i>0 && used[nums[i]+100]==1){
                continue;
            }
            if(path.size()>0 && nums[i] < path.get(path.size()-1)){
                continue;
            }
            used[nums[i]+100]=1;
            path.add(nums[i]);
            find(i+1,nums);
            path.remove(path.size()-1);
        }
    }

    private List<List<Integer>> list = new ArrayList<List<Integer>>();
    private List<Integer> path = new ArrayList<Integer>(); 
    
}

本题的used是局部变量,每层迭代的时候会重新定义,用于每一层的遍历。
同时,答案是收集到所有的树节点。
不理解的画个树图。

46.全排列

在这里插入图片描述

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        used = new int[nums.length];
        find(0,nums);
        return list;
    }

// 每次从没选中的里面选一个,index无限递增,知道list的size为n
    private void find(int index, int[] nums){
        if(index == nums.length){
            list.add(new ArrayList<>(path));
            return;
        }
        for(int i=0;i<nums.length;i++){
            // 如果未选过,则选
            if(used[i] == 1){
                continue;
             }
            path.add(nums[i]);
            used[i] = 1;
            find(index+1, nums);
            path.remove(path.size()-1);
            used[i] = 0;
        } 
    }



    private List<List<Integer>> list = new ArrayList<List<Integer>>();
    private List<Integer> path = new ArrayList<Integer>(); 
    private int[] used;

}

47.全排列 II

在这里插入图片描述

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        used = new int[nums.length];
        Arrays.sort(nums);
        find(0,nums);
        return list;
    }

    private void find(int index, int[] nums){
        if(path.size() == nums.length){
            list.add(new ArrayList<Integer>(path));
            return;
        }

        for(int i=0;i<nums.length;i++){
            // 如果未使用,赶紧用
            if(used[i] == 1){
                continue;
            }
            if(i>0 && nums[i]==nums[i-1]&&used[i-1]==0){
                continue;
            }
            used[i]=1;
            path.add(nums[i]);
            find(index+1,nums);
            path.remove(path.size()-1);
            used[i]=0;
        }
    }
    private List<List<Integer>> list = new ArrayList<List<Integer>>();
    private List<Integer> path = new ArrayList<Integer>(); 
    private int[] used;
}

332.重新安排行程

在这里插入图片描述
缺乏之前知识,后续再做

51. N皇后

在这里插入图片描述

class Solution {
   public List<List<String>> solveNQueens(int n) {
        // 横向的已占的皇后节点
        used = new int[n];
        String[][] nums = new String[n][n];
        init(nums);
        find(nums,0);
        return list;
    }

    private void find(String[][] nums,int index){
        // 迭代结束
        if(index == nums.length){
            list.add(numToList(nums));
            return;
        }

        // 如果这个点不符合皇后范围,则舍弃
        for(int i=0;i<nums.length;i++){
            if(isValid(nums,index,i)){
                used[i]=1;
                nums[index][i] = "Q";
                // 最重要的地方,这里是index+1!!!!
                find(nums,index+1);
                used[i]=0;
                nums[index][i] = ".";
            }

        }


    }
    private boolean isValid(String[][] chessboard, int row, int col){
        // 单独检查列
        if(used[col]==1){
            return false;
        }

        // 检查45度对角线
        for (int i=row-1, j=col-1; i>=0 && j>=0; i--, j--) {
            if (chessboard[i][j] == "Q") {
                return false;
            }
        }

        // 检查135度对角线
        for (int i=row-1, j=col+1; i>=0 && j<=chessboard.length-1; i--, j++) {
            if (chessboard[i][j] == "Q") {
                return false;
            }
        }
        return true;
    }

    // 初始化二维数组为全部"."
    private void init(String[][] str){
        for (int i = 0; i < str.length; i++) {
            for (int j = 0; j < str.length; j++) {
                str[i][j] = ".";
            }
        }
    }
    // 将数组转变成返回值要求的String
    private List<String> numToList(String[][] str){
        List<String> list = new ArrayList<>();
        for (int i = 0; i < str.length; i++) {
            StringBuilder s = new StringBuilder();
            for (int j = 0; j < str.length; j++) {
                s.append(str[i][j]);
            }
            list.add(s.toString());
        }
        return list;
    }
    private List<List<String>> list = new ArrayList<List<String>>();
    int[] used;  
}

不难,但是复杂。
每次迭代是index+1,而不是之前的i+1,切记!

37. 解数独

在这里插入图片描述
缺乏前面知识,后续再做

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

范大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值