回溯理论基础:
正式开始学习回溯,不把过程抽象为树的话确实是很抽象难以理解。把过程看作树,forloop就相当于横向遍历,递归则是纵向遍历。
回溯可以解决如下类别的问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
伪代码模板如下:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
77:
跟着题解的思路走,比较容易理解,自己重新写的时候把forloop里给递归传入的新startIndex写成了startIndex + 1(正确的应该是 i + 1)。这就导致代码会跳过一些有效叶子节点并重复加入已经遍历过的节点。
startIndex是控制横向遍历的,i是控制每一次横向遍历下的纵向遍历的,不要弄错了。
剪枝的操作虽说在图上能理解,但是到代码层面的话理解起来还是有些吃力。总的来说大概就是:
如果选择一个元素后,剩下的元素数量已经不满足k(结果数组所需要的元素个数)个,那就剪枝,不进去这一层无效遍历。