回溯算法简介
只要有递归,就会有回溯。
本质就是穷举。
回溯法可以解决的问题(摘自-代码随想录):
- 组合问题:N个数里面按照一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按照一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等
回溯法解题三部曲(摘自-代码随想录):
- 回溯函数返回值以及参数
- 回溯函数终止条件:达到什么条件后需要结束递归
- 回溯搜索的遍历过程
LeetCode 77.组合
分析
首先定义两个全局变量,用来保存符合条件的结果以及结果集合
回溯三部曲:
- 回溯函数返回值以及参数
- 将函数命名为backtracking,因为定义了全局变量保存结果,因此在符合条件时,不需要返回值,故为void;
- 因为需要从1-n中取k个数,故需要int型参数n、k;还需一个参数startIndex,用来记录每次递归时的起点
- 回溯函数终止条件:达到什么条件后需要结束递归
- 当每次集合长度达到k时,结束递归,返回上层
- 回溯搜索的遍历过程
- 每次递归使用一次for循环,从startIndex开始,继续往下递归
代码
class Solution {
private:
// 全局变量
vector<vector<int> > results; // 存放符合条件结果的集合
vector<int> path; // 存放符合条件的结果
// 回溯三部曲:
// 1. 回溯函数返回值以及参数
void backtracking(int n, int k, int startIndex) {
// 2. 回溯函数终止条件:达到什么条件后需要结束递归
if(path.size() == k) {
results.push_back(path);
return;
}
// 3. 回溯搜索的遍历过程
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) {
backtracking(n, k, 1);
return results;
}
};
剪枝优化
在回溯三部曲的第三步可以进行优化,如果后续的长度不足以构成我们需要的长度为k的集合时,就退出。因为此时集合内的元素为path.size(),而还需要的元素个数为k-path.size(),故for循环的退出条件为i <= n - (k - path.size()) + 1,+1是因为需要包括左边的元素
优化后的for循环代码为
for(int i = startIndex; i <= n - (k - path.size()) + 1; i ++) {
// 与上边一致
}