回溯基础理论
递归函数下面,纯暴力搜索出结果。
回溯可以解决的问题有
组合问题:在集合中满足条件的小组合
切割问题:问满足条件的字符串的切割方式
求子集个数
排列问题:强调元素的顺序,组合不讲究这个顺序,只看元素。
棋盘问题:n皇后问题
回溯可以抽象为图形结构,抽象为树形结构,n叉树,横向:宽度即集合大小,纵向:深度即递归的深度。
递归函数:
void backtracking(){
if(终止条件){
收集结果,//在叶子节点收集结果,每一轮都收集
return;
}
for(遍历集合元素)
{
处理节点的逻辑;
递归;
回溯,撤销处理结果;//14,撤销4放3得到13,撤销3放2得到12
}
}
组合
回溯三部曲:
1.递归函数的参数和返回值。
2.确定递归的终止条件。
3.确定单层搜索的逻辑。
vector<vector<int>>result; //二维数组,定义为全局变量,最后需要一个大组合把所有小组合以结果集方式返回回去。
vector<int>path;//用来存放符合条件的单一结果
void backtracking(int n,int k,int startIndex)//递归函数
{ }
终止条件:
if(path.size() == k){//k为组合大小,到达叶子节点
result.push_back(path);//收割结果
return;
}
//单层处理逻辑,处理节点,回溯
for(int i = startIndex; i <=n; i++){
path.push_back(i);//处理节点
backtracking(n,k,i+1);//递归,例如从14变21,需要从第一层变为第二层。
path.pop_back();//回溯,例如从12变13,变14,需要把前一个pop释放出去。
}
剪枝优化
每一层for循环进行一次剪枝,剪去不必要的子孩子。
缩小上面单层循环i的大小。
i=startIndex;i<=n;改为i< n -(k-path.size())+1 //此时是左闭右开,从startIndex开始,如果不用左闭右开,视情况而定。至多从n -(k-path.size())开始。
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 - (k - path.size()) + 1; i++) { // 注意优化
path.push_back(i); // 处理节点
backtracking(n, k, i + 1);
path.pop_back(); // 回溯,释放撤销处理的节点
}
}
public:
vector<vector<int>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
};