持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情🚀🚀
前言
很快呀,又结束了一个专题,感觉卡哥安排的题型顺序真的比较好,因为前面有了二叉树的基础,对递归回溯有一个大概的认识,所以这一块很明显的发现比原来要更简单了。
😁😁😁
回溯
概念
引用卡哥的话来说:
回溯是递归的副产品,只要有递归就会有回溯,所以回溯法也经常和二叉树遍历,深度优先搜索混在一起,因为这两种方式都是用了递归。
回溯法就是暴力搜索,并不是什么高效的算法,最多再剪枝一下。
题型
- 组合问题:N个数里面按一定规则找出k个数的集合
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
棋盘问题有点难,打算后面再来
回溯模板
卡哥的伪代码
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
组合问题
类似如下这种题型:
-
终止条件:
一般都是用
path收集叶子节点,再加入到result数组中 -
剪枝:当后续遍历的个数已经不符合题意时,直接跳过
i <= 9 - (k-path.length) + 1 -
去重:有的题型给的数组元素是重复的。
if(i > index && candidates[i]==candidates[i-1])
切割问题
拿这道题举例
难点
这里最难是要想到切割这个思路。 还有个易错的地方就是,防止重头截取
backtracking(index+le);
子集问题
如图:
-
递归函数
const dfs = (index) => { res.push([...path]) for(let i = index; i < nums.length;i++){ path.push(nums[i]); dfs(i+1); path.pop() } }可以不用加终止函数
-
收集
这里收集的是每次经过的结点,而不是单单的叶子结点
-
去重
去重逻辑还是一样
(除了递增子序列)
排列问题
-
used数组
要用
used数组记录一下,以免在同一分支上每次都遍历相同位置的值。 -
去重
比如在47. 全排列 II - 力扣(LeetCode)中,要注意一下判断逻辑
if (nums[i] === nums[i - 1] && !used[i - 1])
小结
组合问题卡哥有视频,感觉对于这部分题型掌握的更好,期待后面卡哥的视频啦~