参考:
这部分主要是参考“代码随想录”
应用场景
回溯法,一般可以解决如下几种问题:
- 组合问题: N个数里面按一定规则找出k个数的集合
- 切割问题: 一个字符串按一定规则有几种切割方式
- 子集问题: 一个N个数的集合里有多少符合条件的子集
- 排列问题: N个数按一定规则全排列,有几种排列方式
- 棋盘问题: N皇后,解数独等等
解题方法
-
首先,画出子树,确定思路,即:
- 横向遍历过程中,startIndex是什么,需要减枝的话,要修改for循环的终止条件
- 向下递归完,需要回溯pop出来
- 每次是在叶子节点进行处理(加入结果);
-
然后,参考回溯算法模板:
- 回溯函数模板返回值以及参数(返回值一般是void,参数一般是输入数据和横向遍历的startIndex)
- 回溯函数终止条件
- 回溯搜索的遍历过程(横向和纵向)
-
根据上面的思路,模板如下:
// 存放结果的数据结构 // (除了设为全局变量外,还可以每次作为函数参数每次传递) List<List<Integer>> result = new ArrayList<>(); LinkedList<Integer> path = new LinkedList<>(); // 返回值一般是void // 参数一般是输入数据和横向遍历的startIndex void backtracking(参数) { // 终止条件 if (终止条件) { 存放结果; // 如:result.add(new ArrayList<>(path)); return; } // 横向遍历 for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) { // 常根据终止条件进行剪枝 处理节点; // 如:path.add(candidates[i]); // 纵向遍历 backtracking(路径,选择列表); // 递归,注意startIndex是否改变 回溯,撤销处理结果 // 如:path.removeLast(); } }
例题:组合问题
-
题目:
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。 -
示例:
输入:n = 4, k = 2
输出:
[ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
-
基础版本(核心):
class Solution { List<List<Integer>> result = new ArrayList<>(); LinkedList<Integer> path = new LinkedList<>(); public List<List<Integer>> combine(int n, int k) { combineHelper(n, k, 1); return result; } /** * 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex * @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。 */ private void combineHelper(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); combineHelper(n, k, i + 1); path.removeLast(); } } }
-
剪枝版本(核心):
- 已经选择的元素个数:path.size();
- 还需要的元素个数为: k - path.size();
- 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历
- 为什么有个+1呢?因为包括起始位置,我们要是一个左闭的集合。
class Solution { List<List<Integer>> result = new ArrayList<>(); LinkedList<Integer> path = new LinkedList<>(); public List<List<Integer>> combine(int n, int k) { combineHelper(n, k, 1); return result; } /** * 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex * @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。 */ private void combineHelper(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++){ path.add(i); combineHelper(n, k, i + 1); path.removeLast(); } } }
易错点
-
数据结构:
/****** 定义 ******/ List<List<Integer>> result = new ArrayList<>(); LinkedList<Integer> path = new LinkedList<>(); /****** 操作 ******/ // 添加 result.add(new ArrayList<>(path)); // 添加 path.add(candidates[i]); // 弹出 path.removeLast();
也可以:
/****** 定义 ******/ List<List<Integer>> ans = new ArrayList<List<Integer>>(); List<Integer> combine = new ArrayList<Integer>(); /****** 操作 ******/ // 添加 result.add(new ArrayList<Integer>(path)); // 添加 path.add(candidates[i]); // 弹出 path.remove(path.size() - 1);
-
注意:在横向遍历的时候,注意是对
i
进行操作,不是startIndex
,我这里总是错!!!