什么是回溯

回溯法也可以叫做回溯搜索法,它是一种搜索的方式。
在二叉树系列中,我们已经不止一次,提到了回溯,例如二叉树:以为使用了递归,其实还隐藏着回溯(opens new window)
回溯是递归的副产品,只要有递归就会有回溯。

应用场景

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

理解回溯法

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

递归就要有终止条件,所以必然是一颗高度有限的树(N叉树)。

回溯法模板

回溯法三步曲

1、确定方法参数

伪代码如下:

  1. void backtracking(参数)

2、确定终止条件

伪代码如下:

  1. if (终止条件) {
  2. 存放结果;
  3. return;
  4. }

3、搜索遍历

回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

如图:
image.png
集合的大小决定了for循环横向遍历,递归的深度(即:到达条件时递归的深度)决定树的深度。

伪代码如下:

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

模板代码

综上,回溯算法模板框架,如下:

  1. void backtracking(参数) {
  2. if (终止条件) {
  3. 存放结果;
  4. return;
  5. }
  6. for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
  7. 处理节点;
  8. backtracking(路径,选择列表); // 递归
  9. 回溯,撤销处理结果
  10. }
  11. }

参考

回溯算法理论基础

算法案例

组合

电话号码的字母组合

题解

  1. const letterCombinations = function (digits) {
  2. const digitsLen= digits.length;
  3. const letterMap = [" ", " ", "abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"];
  4. // 处理非法情况
  5. if (!digitsLen) return [];
  6. // 处理长度为1的情况
  7. if (digitsLen === 1) {
  8. return letterMap[digits[0]].split('');
  9. }
  10. const res = [], path =[];
  11. backtracking(0);
  12. return res;
  13. function backtracking(index) {
  14. if (index === digitsLen) {
  15. res.push(path.join('').trim());
  16. return;
  17. }
  18. for(const v of letterMap[digits[index]]) {
  19. path.push(v);
  20. backtracking(index + 1);
  21. path.pop();
  22. }
  23. }
  24. };
  25. const res = letterCombinations('21');
  26. console.log(res)

组合

图解

[77] 组合.png

题解

  1. /**
  2. * @param {number} n
  3. * @param {number} k
  4. * @return {number[][]}
  5. */
  6. var combine = function(n, k) {
  7. // n - 集合遍历的广度
  8. // k - 递归的深度
  9. // 存放结果
  10. var res = [];
  11. // 符合条件单一结果
  12. var item = [];
  13. // 遍历集
  14. var list = [];
  15. for (var i = 0; i < n; i++) {
  16. list.push(i+1);
  17. }
  18. backtracking(0, list)
  19. function backtracking(startIndex, path) {
  20. if (item.length === k) { // 符合条件终止
  21. // 存入结果
  22. res.push([].concat(path));
  23. return;
  24. }
  25. for (var i = startIndex, l = list.length; i < l; i++) { // 遍历本节点子集
  26. var trackItem = list[i];
  27. item.push(trackItem);
  28. backtracking(i+1, item);
  29. // 回溯
  30. item.pop();
  31. }
  32. }
  33. return res;
  34. };

组合总和

图解

33组合总和.png

组合总和ii

图解

33组合总和ii.png

组合总和iii

图解

216组合总和iii.png

分割问题

分割回文串

图解

131分割回文串.png

复原 IP 地址

图解

93复原IP地址.png

子集问题

子集

图解

78子集.png

子集 II

图解

90子集 II.png

排列问题

全排列

图解

46全排列.png

全排列 II

图解

47全排列 II.png