回溯基本概念
回溯是递归的副产品,只要有递归就会有回溯。
回溯法就是暴力搜索,并不是什么高效的算法,最多在剪枝一下。
回溯算法能解决如下问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 棋盘问题:N皇后,解数独等等
组合
- 力扣题目链接
- 我们开始用回溯法解决第一道题目,组合问题
- 此时大家应该深有体会回溯法的魅力,用递归控制for循环嵌套的数量!
- 本题我把回溯问题抽象为树形结构,可以直观的看出其搜索的过程:
- for循环横向遍历,递归纵向遍历,回溯不断调整结果集。
- 朴素版实现:
vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件结果
void backtracking(int n, int k, int startIndex){
// 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(); // 回溯
}
}
vector<vector<int>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
- 剪枝:仅修改
i <= n - (k - path.size()) + 1
组合总和III
- 力扣题目链接
- 多了一个sum的参数而已:
vector<vector<int>> result;
vector<int> path;
void backtracking(int k, int n, int sum, int startIndex){
if(path.size()==k && sum==n) {
result.push_back(path); return;
}
for(int i=startIndex; i<=9; i++){
sum+=i;
path.push_back(i);
backtracking(k, n, sum, i+1);
sum-=i; //回溯
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(k, n, 0, 1);
return result;
}
- 剪枝优化:
vector<vector<int>> result;
vector<int> path;
void backtracking(int k, int n, int sum, int startIndex){
if(sum>n) return; // 优化1
if(path.size()==k && sum==n) {
result.push_back(path); return;
}
// 优化2
for(int i=startIndex; i<=9-(k-path.size())+1; i++){
sum+=i;
path.push_back(i);
backtracking(k, n, sum, i+1);
sum-=i; //回溯
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(k, n, 0, 1);
return result;
}
- 终极优化版(省略sum):
vector<vector<int>> result;
vector<int> path;
void backtracking(int k, int n, int startIndex){
if(n<0) return;
if(path.size()==k && n==0) {
result.push_back(path); return;
}
for(int i=startIndex; i<=9-(k-path.size())+1; i++){
path.push_back(i);
backtracking(k, n-i, i+1);
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(k, n, 1);
return result;
}
电话号码的字母组合
- 力扣题目链接
- 开始用多个集合来求组合,还是熟悉的模板题目
const string letterMap[8]={
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
vector<string> results;
string path;
void backtracking(const string& digits, int index){
if(index==digits.size()) {
results.push_back(path); return;
}
int digit=digits[index]-'0';
string letters=letterMap[digit-2];
for(int i=0; i<letters.size(); i++){
path.push_back(letters[i]);
backtracking(digits, index+1); // 注意:不是i+1
path.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if(digits.empty()) return results; // 别忘了这个朴素情况
backtracking(digits, 0);
return results;
}
参考资料
[1] 代码随想录
[2] Leetcode题解