回溯的基本思想是从一个初始状态开始,按照一定的规则向前探索,寻找问题的解。
如果发现状态不对或者无法达到目标就会退回到上一个状态,重新选择另一种可能。
回溯的模板如下:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择本层集合中元素(画成树,就是树节点孩子的大小)){
处理节点;
backtracking();
回溯,撤销处理结果;
}
}
这代码和N叉树的遍历过程是极其相似的
public static void treeDFS(TreeNode root) {
//递归必须要有终止条件
if (root == null){
return;
}
// 处理节点
System.out.println(root.val);
//通过循环,分别遍历N个子树
for (int i = 1; i <= nodes.length; i++) {
treeDFS("第i个子节点");
}
}
但是还是有所区别,回溯的模板比N叉树的遍历过程多了一个手动撤回的操作,正好对应了回溯里如果发现状态不对就退回到上一个状态的思想。
最后就放一个经典题目,来看看回溯思想落在实战中长什么样
LeetCode77 :给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
例如,输入n=4,k=2,则输出:
[[2,4], [3,4], [2,3], [1,2], [1,3], [1,4]]
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> resultList = new ArrayList<>();
if (k <= 0 || n < k) {
return resultList;
}
// 用户返回结果
Deque<Integer> path = new ArrayDeque<>();
dfs(n, k, 1, path, res);
return res;
}
public void dfs(int n, int k, int startIndex, Deque<Integer> path, List<List<Integer>> resultList) {
// 递归终止条件是:path 的长度等于 k
if (path.size() == k) {
resultList.add(new ArrayList<>(path));
return;
}
// 针对一个结点,遍历可能的搜索起点,其实就是枚举
for (int i = startIndex; i <= n; i++) {
// 向路径变量里添加一个数,就是上图中的一个树枝的值
path.addLast(i);
// 搜索起点要加1是为了缩小范围,下一轮递归做准备,因为不允许出现重复的元素
dfs(n, k, i + 1, path, resultList);
// 递归之后需要做相同操作的逆向操作,具体后面继续解释
path.removeLast();
}
}