回溯算法基础
回溯算法与递归如影随形,有递归的地方就有回溯。通常在递归函数的下面是回溯的逻辑,人们通常说的回溯函数其实是递归函数,因为没有专门用于回溯的一个函数。回溯搜索也是一种纯暴力的搜索方法。
回溯法,一般可以解决如下几种问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
回溯算法模板:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
回溯三步曲:
1.确定递归函数参数返回值。2.确定终止条件。3.确定单层搜索逻辑。
77.组合问题
for循环暴力解决:
#include<iostream>
using namespace std;
int main(){
int n,k;//在这里K的作用是规定使用几轮for循环来解决
cin >> n;
for(int i = 1;i <= n;i++){
for(int j = i + 1;j <= n;j++){
cout << i << j <<' ';
}
printf("\n");
}
return 0;
}
回溯算法的大体思路:
运用两个动态数组,一个一维动态数组用来存放长度为K的组合,一个二维动态数组用来存放一维数组,本层搜索逻辑是找到开始下标startindex到n的数字区间中长度为k的组合,本层搜索的终止条件是一维数组长度等于k。
代码:
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backingTracking(int n,int k,int startindex){
if(path.size() == k){//当一维数组中存在长度为k的组合,就将此组合放入二维数组
result.push_back(path);
return;
}
for(int i = startindex;i <= n;i++){
path.push_back(i);
backingTracking(n,k,i+1);//进入下轮递归,本轮第一个数字已经固定住,然后将开始下标加1,去遍历可以组合的第二个数字
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
path.clear();
result.clear();
backingTracking(n,k,1);
return result;
}
};
优化:本题可以优化的地方就是,每层for循环的起始位置,如果for循环的起始位置之后的数字已经不足k,后面就没必要再遍历
for(int i = startindex;i <= n - (k - path.size()) + 1;i++)
216.组合总和III
解题思路:
本题解题思路与上题类似,只是增加了一个计算和的过程,我们可以直接套模板,终止条件就是一维数组长度达到k且和与目标和相等,回溯的过程要将出初始下标数字以外的数字减掉并弹出。
具体代码如下:
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backTracking(int targetsum,int k,int sum,int startindex){
if(path.size() == k){
if(targetsum == sum){
result.push_back(path);
return;
}
}
for(int i = startindex;i <= 9;i++){
sum += i;
path.push_back(i);
backTracking(targetsum,k,sum,i + 1);
//回溯过程
sum -= i;//比如说path中现在有[1,2],只有将2减掉,弹出,再将3推入,才有新的组合
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
path.clear();
result.clear();
backTracking(n,k,0,1);
return result;
}
};
17.电话号码的字母组合
解题思路:
我们首先要对数字对应的字符串做一个映射,可以选择用一个二维数组或者Map。回溯函数的参数是字符串digits以及记录当前字符串下标的index。终止条件就是当digits字符串中的字符长度等于index,就将此组合存入result中,本级递归的目标是找到符合条件的集合,从下标为0开始,因为每个数字代表的是一组不同的字符串。
具体代码如下:
class Solution {
private:
const string letterMap[10]{
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz",
};
public:
string s;
vector<string> result;
void backTracking(const string& digits,int index){//index记录到第几个数字
if(digits.size() == index){
result.push_back(s);
return;
}
int digit = digits[index] - '0';//将index指向的字母转换为数字
string letters = letterMap[digit];//找到数字对应的字符串
for(int i = 0;i < letters.size();i++){//i从0开始,因为每个数字代表的是不同的集合,所以目标数字的集合都需要遍历
s.push_back(letters[i]);
backTracking(digits,index + 1);
s.pop_back();//回溯的过程
}
}
vector<string> letterCombinations(string digits) {
s.clear();
result.clear();
if(digits.size() == 0){
return result;
}
backTracking(digits,0);
return result;
}
};