任务日期:6.27,今天开始回溯算法的章节,由于最近期末周所以递归章节学得不好,希望到了假期可以好好补补。现在尽可能地认真学习回溯算法,避免欠债。
回溯算法
1.用途:
代码模版:
void backtracking(参数) {
if (终止条件) {//确定递归终止条件
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {//确定单层递归逻辑
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
题目一链接:77. 组合 - 力扣(LeetCode)
思路:1.确定函数返回值和参数:在这里要定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合,所以我定义全局变量了;startIndex 就是防止出现重复的组合。
2.确定终止条件:path这个数组的大小如果达到k,说明我们找到了一个子集大小为k的组合了,在图中path存的就是根节点到叶子节点的路径。
3.确定单层递归逻辑:for循环每次从startIndex开始遍历,然后用path保存取到的节点i。可以看出backtracking(递归函数)通过不断调用自己一直往深处遍历,总会遇到叶子节点,遇到了叶子节点就要返回。backtracking的下面部分就是回溯的操作了,撤销本次处理的结果,方便返回上一层可以继续填入值。
代码:
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 - (k - path.size()) + 1;i ++) { //减枝操作:i的最大的开始值。
path.push_back(i);//处理节点
backtracking(n,k,i + 1);//递归,startIndex应该从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
题目二链接:216. 组合总和 III - 力扣(LeetCode)
思路:1.确定递归函数参数:和77. 组合 (opens new window)一样,依然需要一维数组path来存放符合条件的结果,二维数组result来存放结果集。这里我依然定义path 和 result为全局变量。
2.确定递归终止条件:所以如果path.size() 和 k相等了,就终止;当sum的值比n大时,也就终止了。
3.确定单层递归逻辑:处理过程就是 path收集每次选取的元素,相当于树型结构里的边,sum来统计path里元素的总和。然后进行递归和回溯
代码:
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backtracking(int k,int n,int sum,int startIndex) {
//确定递归终止条件
if(sum > n) return;//剪枝操作:当sum大于n时,当前递归就可以结束了
if(path.size() == k) {
if(sum == n) result.push_back(path);//sum等于n的时候才计入result里
return;
}
//单层递归逻辑
for(int i = startIndex;i <= 9 - (k - path.size()) + 1;i ++) {//剪枝操作
path.push_back(i);
sum += i;//递归要加值
backtracking(k,n,sum,i + 1);// 注意i+1调整startIndex
sum -= i;//回溯要减值
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(k,n,0,1);//在这把sum的值传入;starIndex防止答案重复
return result;
}
};
难点:
题目三链接:17. 电话号码的字母组合 - 力扣(LeetCode)
思路:
代码:
class Solution {
public:
const string lettermap[10] = {
"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz",
};
string s;
vector<string> result;
void backtracking(const string& digits,int index) {//index 表示当前遍历到第几个数字,也是递归的深度
if(index == digits.size()) {
result.push_back(s);
return;
}
int digit = digits[index] - '0';//把数字字符串里的数转化为int值
string letters = lettermap[digit];//然后将字符串数组里的字符串传给当前字符串
for(int i = 0;i < letters.size();i ++) {//递归宽度的遍历
s.push_back(letters[i]);
backtracking(digits,index + 1);
s.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if(digits.size() == 0) return result;//注意字符串的空“”和真空不一样,所以这里要加个特判
backtracking(digits,0);//注意index初始值是0,字符数组的下标
return result;
}
};
难点:
解释细节1: