回溯算法理论基础
什么是回溯算法:
本质为暴力查找,隐藏在递归的下面,就是一个“撤销处理结果”的过程
解决的问题:
- 组合问题:N个数里面按一定规则找出k个数的集合,强调元素不强调顺序
- 切割问题:一个字符串按一定规则(例:回文)有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式,强调顺序
- 棋盘问题:N皇后,解数独等等
如何理解回溯算法:
回溯算法本质都是找子集问题,因此可以抽象成一棵N叉树,集合的大小为树的宽度,递归的层数为树的深度。
回溯搜索的遍历过程:
回溯算法的模板:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
77.组合
题目内容:给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
代码:
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
vector<vector<int>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
void backtracking(int n, int k, int start) {
if(path.size() == k) {
result.push_back(path);
return;
}
for(int i = start; i <= n; i++) {
path.push_back(i);
backtracking(n, k, i+1);
path.pop_back();
}
}
};
反思:回溯算法三部曲:确定回溯算法参数(比递归的参数要难确定一些,可以边写变确定)、确定终止条件(收集结果)、确定单层递归的逻辑(注意还要把收集到的结果挤出去);
题目要求收集所有长度为k的组合,那么终止条件就是中间结果长度达到了k;
在收集的过程中,1 2、1 3、1 4、2 3等组合,如何使得收集到一个结果后,下一个组合从下一个数开始呢?那就是使用start,每次递归的时候start加1,就能从下一个数开始收集;
中间结果path收集到一个数之后记得把收集到的数再弹出,这样才能一直更新而不会累加。