⭐理论基础:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
视频讲解https://www.bilibili.com/video/BV1cy4y167mM/文章讲解
https://programmercarl.com/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html
// 回溯模板
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
视频讲解https://www.bilibili.com/video/BV1ti4y1L7cv文章讲解
https://programmercarl.com/0077.%E7%BB%84%E5%90%88.html
![](https://img-blog.csdnimg.cn/e4d9a05a7e194f008c3621e2d8ad70bf.png)
每次搜索到了叶子节点,我们就找到了一个结果
- 思路:三部曲
- 参数:定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。int 型变量 n 、k 👉从集合 n 里面取 k 个数;int 型变量 startIndex👉记录本层递归中从哪里开始遍历。
vector<vector<int>> result; // 存放符合条件结果的集合 vector<int> path; // 用来存放符合条件单一结果 void backtracking(int n, int k, int startIndex)
- 终止条件:path 这个数组的大小如果达到 k,说明我们找到了一个子集大小为 k 的组合了,在图中 path 存的就是根节点到叶子节点的路径。此时用 result 二维数组,把 path 保存起来,并终止本层递归。
if (path.size() == k) { result.push_back(path); return; }
- 单层递归逻辑:for循环每次从 startIndex 开始遍历,然后用 path 保存取到的节点 i。
for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历 path.push_back(i); // 处理节点 backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始 path.pop_back(); // 回溯,撤销处理的节点 }
- 剪枝优化:如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。
![](https://img-blog.csdnimg.cn/771127d99054465785befd29700a4db0.png)
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置
※注意:
- 本次递归的 i 还没放到 path 中,所以 path.size() 是之前已选取元素个数,那么 n - (path.size()) 是从包括本次递归往后还需要选取的个数;
- 当 i 取 ? 时,? ~ n 恰好会在后续操作中全部放入 path,故 i 最大取到 ?
- 代码:
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;
}
};
// 剪枝优化
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;
}
};