回溯是递归的副产品,有递归就会有回溯。回溯算法的本质就是穷举,因此效率并不高,顶多采用剪枝的方式使之高效一些。
可以解决的问题:
回溯算法解决的问题都可以抽象为树形结构。要解决的问题都可以看做在集合中找子集,集合的大小构成了树的宽度,递归的深度就是树高。抽象如下:
回溯函数其实就是递归函数,模板其实与递归也差不多。
void backtracking(参数){
if(终止条件){
存放结果;
return;
}
for(选择的元素 : 本层集合中元素(孩子节点的数目就是集合的大小)){
处理节点;
backtracking(路径, 选择列表);
回溯操作,撤销处理结果;
}
}
从 [1, n] 中选 k 个数进行组合,将这个穷举过程抽象为 n 叉树的遍历,每一层的可选数目等于当前遍历集合的大小。
class Solution{
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(int startIndex, int n, int k){
if(path.size() == k){
result.push_back(path);
return;
}
for(int i = startIndex; i <= n; i++){ // 从startIndex开始,下一次递归+1
path.push_back(i);
backtracking(i + 1, n, k); // 递归
path.pop_back(); // 回溯
}
}
vector<vector<int>> combine(int n, int k){
backtracking(1, n, k);
return result;
}
};
如果是 n = 4, k = 4 的话,那么第一层 for 循环的时候,从元素 2 开始的遍历都没有意义了,当递归到下一层时从元素 3 开始的遍历都没有意义了。当前递归层中还要添加的元素数目为 k - path.size(),同时要注意从 n - (k - path.size() - 1) 到 n 才是 k - path.size() 个元素。
class Solution{
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(int startIndex, int n, int k){
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(i + 1, n, k);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k){
backtracking(1, n, k);
return result;
}
};