代码随想录算法训练营Day22

回溯法

理论基础

  1. 回溯法解决的问题:
  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等
  1. 如何理解回溯法
    抽象为树型结构,集合的大小构成了树的宽度,递归的深度构成的树的深度。
  2. 回溯模板

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

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

组合问题

给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]

直接的想法是一个双层的for循环,但是当k=50时,这种想法就不可行了,因此需要其他方法,这就是回溯算法

class Solution {  
    List<List<Integer>> result= new ArrayList<>();  
    LinkedList<Integer> path = new LinkedList<>();  
    public List<List<Integer>> combine(int n, int k) {  
        backtracking(n,k,1);  
        return result;  
    }  
  
    public void backtracking(int n,int k,int startIndex){  
        if (path.size() == k){  
            result.add(new ArrayList<>(path));  
            return;  
        }  
        for (int i =startIndex;i<=n;i++){  
            path.add(i);  
            backtracking(n,k,i+1);  
            path.removeLast();  
        }  
    }  
}

组合问题的剪枝操作

例如n=4,k=4,此时有很多组合是不可能的,剪枝操作即将这些可能性省略,从而优化代码
代码中的n-(k-path.size())+1是怎么来的?
path.size():已经选择的元素的个数, k-path.size():还要选的元素的个数,n-(k-path.size()):下标的最大值,可以带入进n=4,k=3验证一下

class Solution {  
    List<List<Integer>> result= new ArrayList<>();  
    LinkedList<Integer> path = new LinkedList<>();  
    public List<List<Integer>> combine(int n, int k) {  
        backtracking(n,k,1);  
        return result;  
    }  
  
    public void backtracking(int n,int k,int startIndex){  
        if (path.size() == k){  
            result.add(new ArrayList<>(path));  
            return;  
        }  
        for (int i =startIndex;i<=n-(k-path.size())+1;i++){ //这里的n-(k-path.size())+1是怎么来的,画图,找规律
            path.add(i);  
            backtracking(n,k,i+1);  
            path.removeLast();  
        }  
    }  
}

组合综合3

class Solution {  
    //注意这里的n是元素和,k是元素个数,和组合不一样  
    List<Integer> path = new ArrayList<>();  
    List<List<Integer>> result= new ArrayList<>();  
    public List<List<Integer>> combinationSum3(int k, int n) {  
        backtracking(k,n,0,1);  
        return result;  
    }  
    void backtracking(int k,int n,int sum,int startIndex){  
        if(sum>n){//剪枝  
            return;  
        }  
        if(sum == n && path.size() == k){  
            //result.add(path);//这里是错的,其实加的是同一个引用,后续path变化会影响result里的内容  
            result.add(new ArrayList<>(path));  
            return;  
        }  
        for(int i = startIndex;i <= 9 - (k - path.size()) + 1;i++){//这里的剪枝剪的是集合个数  
            path.add(i);  
            sum+=i;  
            backtracking(k,n,sum,i+1);  
            sum-=i;//这里别忘了  
            path.removeLast();  
        }  
    }  
  
}

电话号码的字母组合

class Solution {  
  
    //设置全局列表存储最后的结果  
    List<String> list = new ArrayList<>();  
    StringBuilder temp = new StringBuilder();  
    String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};  
  
    public List<String> letterCombinations(String digits) {  
        if (digits == null || digits.length() == 0) {  
            return list;  
        }  
        //初始对应所有的数字  
        backTracking(digits, 0);  
        return list;  
  
    }  
      
    public void backTracking(String digits, int num) {//这里的num与之前的startIndex不同,这里是遍历到digits中的哪一位,不用startIndex做去重,因为从两个集合取元素  
        if (num == digits.length()) {  
            list.add(temp.toString());  
            return;  
        }  
        String str = numString[digits.charAt(num) - '0'];  
        for (int i = 0; i < str.length(); i++) {  
            temp.append(str.charAt(i));  
            backTracking(digits, num + 1);  
            temp.deleteCharAt(temp.length() - 1);  
        }  
    }  
}
posted @ 2025-04-18 11:21  Anson_502  阅读(16)  评论(0)    收藏  举报