今天是回溯算法。
1.回溯算法理论基础
回溯法也可以叫做回溯搜索法,它是一种搜索的方式。
回溯函数也就是递归函数,指的都是一个函数。
回溯的本质就是穷举,穷举所有的可能,然后选出我们想要的答案,如果想要让回溯高效一些,可以加一些剪枝的操作,但也改变不了回溯法就是穷举的本质。
因为没得选,一些问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法。
回溯法解决的问题
组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等
组合是不强调元素顺序的,排列是强调元素顺序。
回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构!
因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。
回溯和递归是相辅相成的。
2.leetcode 77.组合
class Solution {
public:
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();// 回溯,撤销处理的节点
}
}
vector<vector<int>> combine(int n, int k) {
backtracking(n,k,1);
return result;
}
};
思路总结:这道题的题解可以看做回溯算法题目的模板,下次写的时候就可以看一下,这样就会有一个大致的思路。为什么要pop_back?因为如果不加这个的话,加了1之后再加上2,这样会一直把数字加进去,这样就没有13这个组合了,更没有后面的组合了。
3.leetcode 216.组合总和III
class Solution {
public:
vector<vector<int>> result;// 存放结果集
vector<int> path;// 符合条件的结果
// targetSum:目标和,也就是题目中的n。
// k:题目中要求k个数的集合。
// sum:已经收集的元素的总和,也就是path里元素的总和。
// startIndex:下一层for循环搜索的起始位置。
void backtracking(int targetSum,int k,int sum,int startIndex){
if(path.size()==k){
if(sum==targetSum) result.push_back(path);
return;// 如果path.size() == k 但sum != targetSum 直接返回
}
for(int i=startIndex;i<=9;i++){
sum+=i;// 处理
path.push_back(i);// 处理
backtracking(targetSum,k,sum,i+1);// 注意i+1调整startIndex
sum-=i;// 回溯
path.pop_back();// 回溯
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(n,k,0,1);
return result;
}
};
思路总结:相对来说加了元素总和的限制,这道题依然是把问题抽象为树形结构,然后按照回溯模板进行解决。
4.leetcode 17.电话号码的字母组合
class Solution {
public:
const string letterMap[10]={
"",//0
"",//1
"abc",//2
"def",//3
"ghi",//4
"jkl",//5
"mno",//6
"pqrs",//7
"tuv",//8
"wxyz",//9
};
vector<string> result;
string s;
void backtracking(const string& digits,int index){
if(index==digits.size()){
result.push_back(s);
return;
}
int digit=digits[index]-'0';// 将index指向的数字转为int
string letters=letterMap[digit];// 取数字对应的字符集
for(int i=0;i<letters.size();i++){
s.push_back(letters[i]);// 处理
backtracking(digits,index+1);// 递归,注意index+1,一下层要处理下一个数字了
s.pop_back(); // 回溯
}
}
vector<string> letterCombinations(string digits) {
if(digits.size()==0){
return result;
}
backtracking(digits,0);
return result;
}
};
注意:输入1 * #按键等等异常情况
代码中最好考虑这些异常情况,但题目的测试数据中应该没有异常情况的数据,所以我就没有加了。
但是要知道会有这些异常,如果是现场面试中,一定要考虑到!
思路总结:
本题是多个集合求组合,所以在回溯的搜索过程中,都有一些细节需要注意的。
其实本题不算难,但也处处是细节,大家还要自己亲自动手写一写。