代码随想录算法训练营第二十四天 | 回溯算法 77.组合
回溯算法理论基础
- 回溯是递归的副产品,只要有回溯就会有递归
- 回溯的本质是琼剧,所以效率不高
回溯法可以解决的问题
- 组合问题
- 切割问题
- 子集问题
- 排列问题
- 棋盘问题
如何理解回溯
- 回溯算法的问题都可以抽象为树形结构
- 集合的大小就构成了书的快读,递归的深度就构成了树的深度
回溯法模板
回溯三部曲
-
回溯函数返回值及参数
返回值一般为void,参数通常不固定,先写逻辑需要什么参数再传什么参数void backtracking(参数)
-
回溯函数终止条件
一般来说搜索到叶子节点就可以回溯了if(终止条件) { 存放结果; return; }
-
回溯搜索的遍历过程
for(选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) { 处理节点; backtracking(路径,选择列表); // 递归 回溯,撤销处理结果 }
-
整体框架
void backtradking(参数) { if(终止条件) { 存放结果; return; } for(选择:本层集合中的元素(树中节点孩子的数量就是集合的大小)) { 处理节点; backtracking(路径,选择列表); // 递归 回溯,撤销处理结果 } }
77.组合
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<int> path;
vector<vector<int>> ans;
backtracking(n, k, path, ans, 1);
return ans;
}
void backtracking(int n, int k, vector<int>& path, vector<vector<int>>& ans, int startIndex) {
// 终止条件
if(path.size() == k) {
ans.push_back(path);
return ;
}
for(int i = startIndex; i <= n; ++i) {
// 处理节点
path.push_back(i);
// 递归
backtracking(n, k, path, ans, i + 1);
// 回溯
path.pop_back();
}
}
};
剪枝优化
思路:当集合中剩余的元素不够还需要的元素个数时,应该终止遍历
还需要:k - path.size()
剩余:n - i + 1;
n - i + 1 >= k -path.size() --> i <= n - k + path.size() + 1;
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<int> path;
vector<vector<int>> ans;
backtracking(n, k, path, ans, 1);
return ans;
}
void backtracking(int n, int k, vector<int>& path, vector<vector<int>>& ans, int startIndex) {
// 终止条件
if(path.size() == k) {
ans.push_back(path);
return ;
}
// 修改循环终止条件进行剪枝
for(int i = startIndex; i <= n - k + path.size() + 1; ++i) {
// 处理节点
path.push_back(i);
// 递归
backtracking(n, k, path, ans, i + 1);
// 回溯
path.pop_back();
}
}
};