代码随想录算法训练营第二十四天|77.组合

93 阅读2分钟

回溯理论基础

定义

回溯方法就是一种暴力搜索的方法,穷举出所有可能的结果,然后找到想要的答案。回溯函数就是递归函数(有递归也就必定会有回溯),回溯一般隐藏在递归调用的下面一句

常用于解决的问题

1、组合问题:从N个数里面按照一定的规则找到k个数的组合

2、切割问题:一个字符串按照某种规则有几种切割方法

3、子集问题:一个集合中有多少符合条件的子集

4、排列问题:N个数按照规则全排列,有几种排列方式

5、棋盘问题:N皇后、解数独等

如何理解

所有回溯算法解决的问题都可以抽象为树形结构!因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度

算法模板

因为回溯函数就是递归函数,所以解题时仍要用到递归三部曲

回溯函数的返回值一般为void。回溯函数中的for循环就是树的横向遍历,递归就是树的纵向遍历。

回溯函数模板

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

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

77.组合

题目链接:77. 组合

思路:将组合问题抽象成树形结构,然后用回溯法进行搜索,在叶子节点中收集结果。

该题目抽象的树形结构如下(引用自随想录网站): 20201123195223940.png

class Solution {
    public List<List<Integer>> result = new ArrayList<>();
    public List<Integer> path = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
    public void backtracking(int n, int k, int start) { // start是起始位置的下标
        if (path.size() == k) {
            List<Integer> temp = new ArrayList<>(path);
            result.add(temp);
            return;
        }
        for (int i = start; i <= n; i++) {
            path.add(i);
            backtracking(n, k, i + 1);
            path.remove(path.size() - 1);
        }
        return;
    }
}

回溯法通过剪枝,会有很大的优化空间。这里举一个例子来说明剪枝过程(引用代码随想录网站)。

20210130194335207.png

class Solution {
    public List<List<Integer>> result = new ArrayList<>();
    public List<Integer> path = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
    public void backtracking(int n, int k, int start) { // start是起始位置的下标
        if (path.size() == k) {
            List<Integer> temp = new ArrayList<>(path);
            result.add(temp);
            return;
        }
        // 对i的搜索范围进行剪枝,如果从path.size() 加上 i 以后的数量已经不足以组合成k个,就不用再遍历了
        for (int i = start; i <= n - (k - path.size()) + 1; i++) { 
            path.add(i);
            backtracking(n, k, i + 1);
            path.remove(path.size() - 1);
        }
        return;
    }
}