系列文章目录
代码随想录——回溯
概述
回溯的本质就是递归遍历,但在完成某一条路之后会撤回到上一层,然后重新选择另一条路继续遍历,是一个比较低效的算法,能进行提升的方式就是剪枝。
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
组合
组合
链接:https://leetcode.cn/problems/combinations/description/
vector<vector < int > > 作为最终返回的结果,vector < int >作为每一小项,中止条件是vector< int > == k,注意在递归过程中n,k是不变的,所以必须引入一个递增index来去重。
剪枝方法:
- 已经选择的元素个数:path.size();
- 还需要的元素个数为: k - path.size();
- 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历
组合III
链接:https://leetcode.cn/problems/combination-sum-iii/description/
与前一个组合几乎一样,但中止条件需要加一个total == k的判断。
电话号码的字母组合
链接:https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/
中止条件是单个项的size等于digits的size,一开始我以为要双循环,即digits遍历和单个数字对应的字母遍历,但其实不需要,因为digits一定是顺序往后取的,所以只需要定义一个index,每层递归+1即可。
ps:在leetcode由char转变为int的方法是减去 ‘0’
num[digits[index] - '0'
组合总和
链接:https://leetcode.cn/problems/combination-sum/description/
中止条件为当总和相等时push进result并return,总和超过target时直接人return;注意这道题也是不能重复的,一般不能重复的都需要加入index来保证起始位置。
组合总和II
链接:https://leetcode.cn/problems/combination-sum-ii/description/
这道题不重复的核心点在于,同层的数不能够重选,例如[1,1,2,2,5],第一层选了1,第二层还是可以继续选1,因为两者不是同层的。但如果第一层的第一个1用完了,又选了第二个1,这就重复了,所以只需要加一个判断条件:
if (i > index && candidates[i] == candidates[i - 1]) {
continue;
}
这里的核心点在于ℹi > index,因为index是本层的第一个数,必然不可能重复,所以要从index下一个开始判断,此时如果第二个数和第一个数是相同的,则属于同层重复选择,直接continue;
分割
分割回文串
https://leetcode.cn/problems/palindrome-partitioning/description/
每切下来一个子字符串,就相当于是在一层做了选择,这样就可以套上回溯算法的模板。中止条件是起始切割位置到了字符串的最后一位,额外再写一个回文判断函数。、
注意:leetcode里常用的切割字符串函数substr,第一个参数是起始位置,第二个参数是要切割的长度。
string str = s.substr(index,i - index + 1);
** 复原ip地址
https://leetcode.cn/problems/restore-ip-addresses/description/
这道题一直做错,能接受的方法是,将切割每一个子字符串进行判断,符合标准后放入vector备用,个数达到4个并且index也等于s.size()之后,再拼接放入最终的result里。
注意:string转为int直接用stoi函数
return stoi(s) <= 255;
字符串的增加和删改不能用-=和+=,要用insert和erase。
子集
子集
https://leetcode.cn/problems/subsets/description/
这道题唯一要注意的点是子集的大小是不固定的,所以不能在中止条件里面将子集添加到结果里,每一次循环都要添加,所以放在递归的开头。中止条件是index == s.size()。
子集II
https://leetcode.cn/problems/subsets-ii/description/
这题和之前组合的去重思路一模一样,在子集的基础上,首先对nums进行sort保证是递增的,然后进行判断去掉本层重复的选择。
if(i > index && nums[i] == nums[i - 1]) continue;
全排列
全排列
https://leetcode.cn/problems/permutations/description/
全排列不需要用到index,index只有不能从头重复拿的问题需要用的,全排列需要从头拿,但怎么保证去重呢,就把取过的数暂存到temp,并将这个数重新赋值一个不会取到的数,回溯过程中再放回来。循环过程中,对数进行判断,如果是那个不会取到的数,就直接跳过,从而达到去重的效果。
全排列II
https://leetcode.cn/problems/permutations-ii/description/
和之前的去重操作一样,先sort然后本层去重。
棋盘问题
N皇后
https://leetcode.cn/problems/n-queens/description/
比较好的思路是先创建一个n*n的棋盘,每一行放置Q的时候,对这个位置进行判断,判断函数的只需要判断这一层之上是否满足就可以了,中止条件是层数 == n。
注意:
- 引用传递会比值传递节省巨幅的空间和时间,尽量使用引用传递。
- for循环的判断条件,用逗号时相当于只有最后一个生效!!!
for (int i = 5, j = 3; i >= 0, j >= 0; i--, j--) {
// 在这里,条件实际上是 j >= 0
// 循环体
cout << "i: " << i << ", j: " << j << endl;
}
for (int i = 5, j = 3; i >= 0 && j >= 0; i--, j--) {
// 在这里,条件是 i >= 0 && j >= 0
// 循环体
cout << "i: " << i << ", j: " << j << endl;
}
***解数独
https://leetcode.cn/problems/sudoku-solver/
写的不好。
这题思路回溯的思想是写两个for循环,然后相当于对每一个空的格子尝试填入数字,如果满足条件则return true,如果9个数字都不满足则回溯。填入数字之前先判断。
其他
***递增子序列
https://leetcode.cn/problems/non-decreasing-subsequences/description/
这道题最大的问题在于不能够sort,并且得保证去重。正常的想法是只要path数量大于2,就放入result。但这样就给去重造成了困难,所以一个很新颖的思路是只有当index == size的时候,才去考虑将结果放入result,这样做的好处是第一个判断条件只要元素大于等于path的最后一个数,就正常迭代,但迭代完成回溯回来的时候,加入了第二个判断条件,即如果元素等于path的最后一个数,因为第一个条件只要大于等于就满足,如果第二个条件判断元素为等于,则说明该元素和上一个元素是相等的,即为同层的重复元素,则直接跳过。非常好的想法!文章来源:https://www.toymoban.com/news/detail-805216.html
重新安排行程
https://leetcode.cn/problems/reconstruct-itinerary/description/
还没写。文章来源地址https://www.toymoban.com/news/detail-805216.html
到了这里,关于代码随想录——回溯的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!