基础理论
0、关于回溯算法可以解决哪些问题?
(1)组合问题,给一个数组1234,元素总和为2的组合有多少个?
(2)切割问题,给一个数组,对数组的元素按照特定的规则切割成新的数组
(3)子集问题,给一个数组,求数组的子集
(4)排列问题,和上面的组合问题相似,但是不强调元素的顺序问题
(5)棋盘问题,N皇后或者解数独问题
1、如何理解回溯法
可以将其总结为一棵树,深度和宽度与回溯的循环有关
回溯算法实质是一个穷举法,所以效率不高,适合一些暴力搜索的题;
2、实现逻辑
void back(参数){
if(终止条件){
收集结果; //因为结果都在叶子节点上,所以遍历到叶子节点上的时候开始收集结果
return ;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)){
处理节点;
递归函数;
回溯算法;
}
return ;
}
第一题:组合
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
方法一:直接for循环暴力求解,但是在数据量很大的时候,效率很低;
方法二:回溯法
三部曲:
(1)递归函数的返回值以及参数
vector<vector<int>> result; //存放符合条件结果的集合
vector<int> path; //用来存放符合条件的单一结果
void backtracking(int n,int k ,int startIndex) //集合n中取k个数,而startIndex用于控制下一层的递归,避免重复
(2)确定终止条件
如果path这个数组的大小如果达到k,说明找到了一个子集大小为k的组合,用result二维数组将path保存起来,终止本层递归
if(path.size()==k){
result.push_back(path);
return ;
}
(3)单层搜索逻辑
横向利用startIndex作为遍历起点,fo循环,然后用path保存取到的节点i
for(int i=startIndex; i<=n; i++){
path.push_back(i); //处理节点
backtracking(n,k,i+1); //递归:控制树的纵向遍历,注意下一层搜索从i+1开始
path.pop_back(); //回溯,撤销处理的节点
}
整体逻辑还是根回溯算法大纲差不多,处理、递归、回溯
第二题:组合总和3
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
0、思路:观察可得,构建的树最大宽度为9,深度为k,按照上题的逻辑即可求得
1、回溯三部曲
(1)确定递归函数
还是需要全局变量,保存路径和返回结果
vector<vector<int>> result;
vector<int> path;
void backtracking(int targetSum,int k,int sum, int startIndex)
(2)确定终止条件
还是一样,path取够k长度就停止
if(path.size()==k){
if(sum==targetSum) result.push_back(path);
return ;
}
(3)单层搜索过程:处理,递归,回溯
for(int i=startIndex;i<=9-(k-path.size())+1;i++){
sum+=i;
path.push_back(i);//处理
backtracking(targetSum,k,sum,i+1); //递归
sum-=i;
path.pop_back(); //回溯
}
关于为什么sum要加了再减,sum是用来寻找合适的值,所以加了然后递归进去判断,为什么要减,是因为一个叶子节点判断完,需要回去判断另一个叶子;有加就有减
注意点:for循环内,i应该根据path的长度以及k的大小来决定,同时避免重复
第三题:电话号码的字母组合
0、思路:首先,0-9分别对应不同的字母集合,所以横向是每个数字对应的字符集合
然后,深度是集合长度,比如23,k=2
先写出每个数字对应的字符组合:
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
1、回溯三部曲
(1)确定回溯函数参数,同时定义全局变量
vector<string> result;
string s;
void backstracking(const string & digits,int index)
(2)确定终止条件
用index控制层数,如果层数到达数组长度,即停止
if(index==digits.size()){
result.push_back(s);
return;
}
(3)确定单层递归逻辑
处理、递归、回溯
int digit=digits[index]-'0';
string letters=letterMap[digit];
for(int i=0;i<letters.size();i++){
s.push_back(letters[i]);
backtracking(digits,index+1);
s.pop_back();
}
第三题需要二刷!