✅✅代码随想录算法训练营Day30 || 回溯小结

127 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情🚀🚀

前言

很快呀,又结束了一个专题,感觉卡哥安排的题型顺序真的比较好,因为前面有了二叉树的基础,对递归回溯有一个大概的认识,所以这一块很明显的发现比原来要更简单了。

😁😁😁

回溯

概念

引用卡哥的话来说:

回溯是递归的副产品,只要有递归就会有回溯,所以回溯法也经常和二叉树遍历,深度优先搜索混在一起,因为这两种方式都是用了递归。

回溯法就是暴力搜索,并不是什么高效的算法,最多再剪枝一下。

题型

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集

棋盘问题有点难,打算后面再来

回溯模板

卡哥的伪代码

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

组合问题

类似如下这种题型:

image.png

  • 终止条件:

    一般都是用path收集叶子节点,再加入到result数组中

  • 剪枝:当后续遍历的个数已经不符合题意时,直接跳过

    i <= 9 - (k-path.length) + 1

  • 去重:有的题型给的数组元素是重复的。

    if(i > index && candidates[i]==candidates[i-1])

切割问题

拿这道题举例 image.png

image.png 难点

这里最难是要想到切割这个思路。 还有个易错的地方就是,防止重头截取

backtracking(index+le);

子集问题

如图:

image.png

  • 递归函数

       const dfs = (index) => {
         res.push([...path])
         for(let i = index; i < nums.length;i++){
             path.push(nums[i]);
             dfs(i+1);
             path.pop()
         }
     } 
    

    可以不用加终止函数

  • 收集

    这里收集的是每次经过的结点,而不是单单的叶子结点

  • 去重

    去重逻辑还是一样(除了递增子序列)

排列问题

image.png

  • used数组

    要用used数组记录一下,以免在同一分支上每次都遍历相同位置的值。

  • 去重

    比如在47. 全排列 II - 力扣(LeetCode)中,要注意一下判断逻辑

    if (nums[i] === nums[i - 1] && !used[i - 1])

小结

组合问题卡哥有视频,感觉对于这部分题型掌握的更好,期待后面卡哥的视频啦~