回溯算法总结一(组合问题)

171 阅读2分钟

LeetCode回溯算法基本套路总结

回溯算法并不是高效的算法,其本质就是穷举,在所有的可能性中获取到自己想要的结果

一、回溯算法能够解决的问题

  • 组合问题:N个数中按照一定规则找出k个数的集合
  • 子集问题:一个N个数的集合中找到符合规则的子集
  • 切割问题:一个字符串按照一定规则切割
  • 排列问题:N个数按照一定规则全排列
  • 棋盘问题:比如N皇后问题

如何理解回溯算法

回溯算法可以将所解决的问题抽象成树形结构,集合的大小构成了树的宽度,递归的深度就是树的深度

基本代码模板

//基本的代码结构
class Solution {

    定义返回的结果 res
    定义返回结果的路径 path
    
    public 返回类型 主函数名(参数) {
       
        backtrack(参数);
        return res;
    }
    
    void backtrack(参数){
        
        if(递归结束条件){
            存放结果到res;
            return;
        }
        
        for(横向遍历){
            处理;
            backtrack(参数);
            回溯;
        }
    }
}

各种类型问题解决方法总结

1、组合问题

1.组合

代表题目:leetcode77:组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例:输入:n=2,k=4 输出[[2,4],[3,4],[2,3],[1,2],[1,3],[1,4]]

提示:

1 <= n <= 20

1 <= k <= n

来源:力扣(LeetCode) 链接:leetcode.cn/problems/co…

代码:

class Solution { 
    //定义返回结果
    public List<List<Integer>> res = new ArrayList(); 
    LinkedList<Integer> path = new LinkedList(); 
    public List<List<Integer>> combine(int n, int k) { 
        if(n < 1){ 
            return res;
           } 
      
        backTrack(n,k,list,1); 
        return res; 
    } 
    public void backTrack(int n, int k,int index){ 
    
        //结束条件,path.size()等于k
        if(path.size() == k){ 
            res.add(new ArrayList(path)); 
            return;
            }
        //循环遍历每一个条件
        for(int i = index; i <= n - (k - path.size())+1; i++){
        
           list.add(i); //处理
           backTrack(n,k,i+1); //递归调用
           list.removeLast(); //回溯
           
           } 
    } 
}

分析:

解题步骤:

1、将问题抽象成树形结构

70315dfe3e4d4a7bcf5425b3346dedf.png

其中注意限制条件:题目中返回k个数的组合,所以只要路径等于k就结束递归,并将路径添加到结果当中, 而且选用过的元素不能再选择,即不能出现[2,2]或者是[2,1]这样的数。

2、编写代码

2.1定义返回结果

    //最终返回结果
    public List<List<Integer>> res = new ArrayList(); 
    //记录路径
    LinkedList<Integer> path = new LinkedList(); 

2.2backTrack方法

//因为不能够选取重复元素,所以使用index参数记录每次选取元素开始的地方
 public void backTrack(int n, int k,int index){ 
    
        //结束条件,path.size()等于k
        if(path.size() == k){ 
            res.add(new ArrayList(path)); 
            return;
            }
        //循环遍历每一个条件
        for(int i = index; i <= n; i++){
        
           list.add(i); //处理
           backTrack(n,k,i+1); //递归调用
           list.removeLast(); //回溯
           
           } 
    } 

2.3 判断是否能够剪枝(算法优化)

将循环遍历中i<=n替换成了i<= n - (k - path.size())+1

1、已经选择元素path.size()

2、还需要元素个数k-path.size()

3、在集合n中至多要从该起始位置k-path.size()++1开始遍历

总的意思就是如果剩余元素个数已经不足够再组合成一个k个元素的集合,遍历已经没有意义

//因为不能够选取重复元素,所以使用index参数记录每次选取元素开始的地方
 public void backTrack(int n, int k,int index){ 
    
        //结束条件,path.size()等于k
        if(path.size() == k){ 
            res.add(new ArrayList(path)); 
            return;
            }
        //循环遍历每一个条件,
        for(int i = index; i <= n - (k - path.size())+1; i++){
        
           list.add(i); //处理
           backTrack(n,k,i+1); //递归调用
           list.removeLast(); //回溯
           
           } 
    }