• 算法通关村18关 | 透析回溯的模板


    回溯有清晰的解题模板,

    1. void backtracking(参数){
    2. if (终止条件){
    3. 存放结果;
    4. return;
    5. }
    6. for (选择本层中的集合元素(画成树,就是树节点孩子的大小) {
    7. 处理节点;
    8. backtracking();
    9. 回溯,撤销处理结果;
    10. }
    11. }

    1. 从N叉树说起

    在回溯之前,先看一下N叉树的遍历问题,我们知道在二叉树中,按照前序遍历的过程如下所示:

    1. void treeDFS(TreeNode root){
    2. if (root == null){
    3. return;
    4. }
    5. System.out.println(root.val);
    6. treeDFS(root.left);
    7. treeDFS(root.right);
    8. }
    9. class TreeNode{
    10. int val;
    11. TreeNode left;
    12. TreeNode right;
    13. }

    假如是N叉树,该如何遍历,将原来的子节点left和right换成list。

    1. public class TreeNode {
    2. int val;
    3. List nodes;
    4. }
    5. //N叉树遍历的基本过程
    6. public static void treeDFS(TreeNode root){
    7. //递归必须要有终止条件
    8. if (root == null){
    9. return;
    10. }
    11. //处理节点
    12. System.out.println(root.val);
    13. //通过循环,分别遍历N个子树
    14. for (int i = 0; i < nodes.length; i++) {
    15. treeDFS("第i个子节点");
    16. }
    17. }

    2. 为什么有的问题暴力搜索也不行

    LeetCode77:给定两个整数n和k,返回1..n中所有可能的k个数的组合。例如,输入n=4,k = 2,则输出:[ [1,2], [1,3], [1,4], [2,3], [2,4], [3,4]。

    可以用双重循环暴力搜索

    1. int n = 4;
    2. for (int i = 0; i <= n; i++) {
    3. for (int j = i + 1; j <= n; j++) {
    4. System.out.println( i + " " + j);
    5. }
    6. }

    如果n和k都变大,例如k=50,那么50层循环嵌套,时间复杂度就会非常高。

    3. 回溯 = 递归 + 局部枚举 + 放下前任

    继续研究LeetCode77题目:按照树的思想

    n=4时,我们可以选择的n有{1,2,3,4}这四种情况,所以 我们从第一层到第二层的分支有四个,分别表示可以取 1,2,3,4.从左向右取数,取过的不会重复取,第一次取1,集合变为2,3,4,因为k为2,我们只需要再取一个就可以了,分别取2,3,4.

    再看k=3,n=5,的树,图中红框标记处,代表一个结果,最后会有空的情况。

    从图中发现元素个数时树的宽度,,每个结果的元素个数相当于树的深度(纵向),所以我们说回溯算法时一纵一横。

    1. 每次选择都是从类似{1,2,3,4},{1,2,3,4,5}这样一个序列一个个选,这就是局部枚举,而且越往后,枚举范围越小。
    2. 枚举时,我们就是简单的暴力测试而已,一个个验证,能否满足要求,从上图可以看到,这就是N叉树遍历的过程,因此两者代码很相似。
    3. 我们再看上图中红色大框起来的部分,这个部分执行过程与n=4,k = 2,的处理完全一样,很明显是个可以递归的子序列。
    4. 放下前任,这步还是根据代码解释。

    回溯代码

    1. public List> combine(int n, int k){
    2. ArrayList> res = new ArrayList<>();
    3. if (k <= 0 || n < k){
    4. return res;
    5. }
    6. //用户返回结果
    7. ArrayDeque path = new ArrayDeque<>();
    8. dfs(n, k, 1, path, res);
    9. return res;
    10. }
    11. private void dfs(int n, int k, int startIndex, Deque path, List> res) {
    12. //递归终止条件是:path的长度等于k
    13. if (path.size() == k){
    14. res.add(new ArrayList<>(path));
    15. return;
    16. }
    17. //针对一个结点,遍历可能搜索的起点,其实就是枚举
    18. for (int i = 0; i <= n; i++) {
    19. //向路径变量添加一个数,就是上图中的一个树枝的值
    20. path.addLast(i);
    21. //搜索起点要加1是为了缩小范围,下一轮递归做准备,因为不允许出现重复元素
    22. dfs(n,k,i + 1,path,res);
    23. path.removeLast();
    24. }
    25. }

    递归中的循环解释:

    1. for (int i = 0; i <= n; i++) {
    2. dfs(n,k,i + 1,path,res);
    3. }

    代表图中的四个分支

    4. 图解为什么有个撤销操作

    主要还是对for循环的理解:

    图中解释,我把最外层的for循环成为外for,内层成为内for

  • 相关阅读:
    SpringCloud Alibaba 入门到精通 - Nacos
    servlet对象的生命周期
    【数据库与事务系列】多数据源切换
    WasmEdge 0.10.0 发布!全新的插件扩展机制、Socket API 增强、LLVM 14 支持
    vue使用scope插槽实现dialog窗口
    Java面向对象编程三大特征-多态
    程序员面试中的测试驱动开发:如何展示你的编程范式
    Spring Mvc 拦截器详解
    element-plus form表单的二次封装
    其实MyBatis的插件机制可以帮我们解决工作很多问题,建议收藏
  • 原文地址:https://blog.csdn.net/m0_54138535/article/details/132793463