理论基础
回溯和递归相辅相成,只要有递归,就有回溯。
回溯其实是一个纯暴力的搜索。
回溯法,一般可以解决如下几种问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
你用暴力,一层层for循环嵌套是不好解决这个问题的,也写不出来这样的代码,需要使用回溯搜索法。
回溯就是一种暴力的搜索方式。
如果想要清晰了解回溯法,最好把回溯法抽象成图形结构。
终止条件:
组合问题,切割问题,排列问题,部分棋盘问题都是在叶子节点,就是收集结果的时候,只有子集问题是在每个节点都要收集结果。
回溯算法模板框架如下:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
77.组合
题目链接:77.组合
难度指数:😀😐😕
回溯算法就是通过递归来控制有多少层for循环,每一层递归都是一个for循环。
path和result我们定义为全局变量,其实可以放到参数里,然后给它定义为一个引用的方式。
我们不将它们放进递归函数的参数里,是怕参数过多,影响代码的可读性。
16:00
startindex,我们每次递归的时候,会把这次要搜索的起始位置传进来,(初始的时候,当然是传入1啦)
代码思路:
void backtracking(n, k, startindex) {
//终止条件
if (path.size() == k) { //就到树形结构的叶子节点了 (即收割结果的时候)
result.push(path); //result数组把结果收集起来
return;
}
//确定单层递归的逻辑
for (i = startindex; i <= n; i++) {
path.push(i);
backtracking(n, k, i + 1);
path.pop();
}
}
AC代码: (核心代码模式)
class Solution {
private:
vector<vector<int>> result; //存放符合条件结果的集合
vector<int> path; //用来存放符合条件结果
void backtracking(int n, int k, int startIndex) {
if (path.size() == k) {
result.push_back(path);
return;
}
//确定单层递归的逻辑
for (int i = startIndex; i <= n; i++) {
path.push_back(i); //处理节点
backtracking(n, k, i + 1); //递归
path.pop_back(); //回溯,撤销处理的节点
}
}
public:
vector<vector<int>> combine(int n, int k) {
result.clear(); //可以不写
path.clear(); //可以不写
backtracking(n, k, 1);
return result;
}
};