正式开始回溯算法!
回溯算法理论基础
回溯算法又叫回溯搜索法,是一种搜索的方式。回溯是递归的副产物,有递归就会有回溯。
回溯法的本质是穷举,所以它本身并不高效。什么问题一定需要回溯呢?
1. 组合问题,n个数里面取k个数的集合
2. 切割问题,一个字符串有几种方式切割
3. 子集问题,一个N个数的集合里有多少符合条件的子集
4. 排列问题,n个数按一定规律全排列
5. 棋盘问题,N皇后,解树独
回溯法解决的问题都可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。
回溯三部曲:
1. 寻找传入参数和返回值
2. 寻找终止条件
3. 回溯搜索的遍历过程
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
大家可以从图中看出for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。
77. 组合
每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围。
图中可以发现n相当于树的宽度,k相当于树的深度。
那么如何在这个树上遍历,然后收集到我们要的结果集呢?
图中每次搜索到了叶子节点,我们就找到了一个结果。
相当于只需要把达到叶子节点的结果收集起来,就可以求得 n个数中k个数的组合集合。
1. 在每一次遍历的时候,除了n和k,还需要一个参数,为int型变量startIndex,这个参数用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
2. 终止条件。path这个数组的大小如果达到k,说明我们找到了一个子集大小为k的组合了。
3. for循环用来横向遍历,递归的过程是纵向遍历。
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(int n, int k, int index){
if(path.size()==k){
result.push_back(path);
return;
}
for(int i=index; i<=n; i++){
path.push_back(i);
backtracking(n, k, i+1);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
result.clear();
path.clear();
backtracking(n, k, 1);
return result;
}
};
剪枝可以把for循环改成:
for(int i=index; i<=n-(k-path.size()); i++)
216.组合总和III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(int n, int k, int startindex){
if(path.size()==k){
int sum = 0;
for(int i=0; i<k; i++){
sum+=path[i];
}
if(sum == n){
result.push_back(path);
}
return;
}
for(int j =startindex; j<=9; j++){
path.push_back(j);
backtracking(n, k, j+1);
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
result.clear();
path.clear();
backtracking(n, k, 1);
return result;
}
};
- 时间复杂度: O(n * 2^n)
- 空间复杂度: O(n)
17.电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母
要解决如下三个问题:
- 数字和字母如何映射
- 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
- 输入1 * #按键等等异常情况
通过画图可以更好理解:
class Solution {
private:
const string lettermap[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:
vector<string> result;
string s;
void backtrack(const string& digits, int index){
if(index==digits.size()){
result.push_back(s);
return;
}
int digit = digits[index]-'0';
string letter = lettermap[digit];
for(int i=0; i<letter.size(); i++){
s.push_back(letter[i]);
backtrack(digits, index+1);
s.pop_back();
}
}
vector<string> letterCombinations(string digits) {
result.clear();
s.clear();
if (digits.size() == 0) {
return result;
}
backtrack(digits, 0);
return result;
}
};