Skip to content

代码随想录 回溯算法:回溯总结

回溯法理论基础

回溯法的本质是递归,可以想象成树理解。

回溯是递归的副产品,只要有递归就会有回溯

回溯法能解决什么问题

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

回溯法模板

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

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

组合问题

名称集合组合元素数量目标和元素可否重复选取
组合1-n个数(无重复数组)k/不可
组合总和III1-9个数(无重复数组)kn不可
组合总和无重复数组/target
组合总和II有重复数组/target不可

有重复数组需要树层去重,借助used数组。

有数量限制就加在终止条件;

有目标和就加一个sum,并加在终止条件;

元素如果可重读选取,startIndex进入递归时从i开始,表示当前元素可重复选;

电话号码的字母组合

多个集合取组合,各个集合之间相互不影响,那么就不用 startIndex。

每个集合中各挑选出一个数组,组成一个组合。各集合相互不影响;

多个无重复集合、组合元素数量给定、无目标和、不可重复选取。

切割问题

分割回文串
  • 将切割问题抽象为组合问题
  • 切割中递归如何终止?startIndex大于等于数组大小
  • 如何截取字串?substr函数
  • 如何判断回文?巧用动规减少循环次数

子集问题

子集问题是不限制个数的组合,所以在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果

子集II 问题针对子集问题进行去重。在组合总和III 已经学会了树层去重,这道题也是一样的套路

递增子序列

子序列类似于子集,但是增加了递增的要求,另外原数组中有重复元素,这又要求我们去重。这就是与子集问题的两个区别。

排列问题

排列就是有序的集合。

排列问题的不同:

  • 每层都是从0开始搜索而不是startIndex
  • 需要used数组记录path里都放了哪些元素了

排列II

又增加了重复元素的条件,因此我们继续树层去重

去重本质

其实去重的本质是标记本层有哪些元素已经使用过了。所以使用set是一个很常见的想法。

但是因为题目一般数组元素的范围都比较小,使用数组来代替更能够节省时间。set的一些操作效率是比较低的,比如不断地哈希映射。

重新安排行程

这道题carl 使用了回溯的解法,后来提交就不通过了。

因为对于死胡同分支额外进行了一次遍历,导致大量无效重复的搜索。

进而使用Hierholzer算法。即深搜+后续遍历的思想,总是将死胡同先入栈。最后逆序输出。

棋盘问题

N皇后

棋盘问题其实更好理解。单层回溯搜索就是一行一行地放皇后,保证当前不与之前冲突的前提下放置。放好后进入递归放下一行。最终最后一行也放置完成了,就搜索到叶子节点,找到一套方案。终止。

解数独

N皇后是每行每列只有两种可能,放或不放。

数独是每行每列有9种可能。比前者深一层。