【算法】回溯法四步走 (4)

注意:递归深度depth,根节点深度为 0,一般根节点代表的解都为空解,所以深度为 0,之后的解深度为 1 才代表我们的第 1 次分支路径选择。

拿全排列来举例:
设计状态变量

首先这棵树除了根结点和叶子结点以外,每一个结点做的事情其实是一样的,即:在已经选择了一些数的前提下,在剩下的还没有选择的数中,依次选择一个数,这显然是一个 递归 结构;

递归的终止条件是: 一个排列中的数字已经选够了 ,因此我们需要一个变量来表示当前程序递归到第几层,我们把这个变量叫做 depth,或者命名为 index ,表示当前要确定的是某个全排列中下标为 index 的那个数是多少;

布尔数组 used,初始化的时候都为 false 表示这些数还没有被选择,当我们选定一个数的时候,就将这个数组的相应位置设置为 true ,这样在考虑下一个位置的时候,就能够以 \(O(1)\) 的时间复杂度判断这个数是否被选择过,这是一种「以空间换时间」的思想。

这些变量称为「状态变量」,它们表示了在求解一个问题的时候所处的阶段。需要根据问题的场景设计合适的状态变量。

2. 寻找递归出口

2.寻找递归出口:一般为某深度,或叶子节点,或非叶子节点(包括根节点)、所有节点等。决定递归出去时要执行的操作。

特别注意:每次提交数组的集合(即 List<List<>>)的时候,都要记得创建一个新的数组来存放结果数组元素(即 new List<>(list)),不然后面操作的都是加入集合后的那个数组。

这里需要注意的是,由于我们的节点状态可能需要多个参数来表示,所以我们的递归出口可能也并不唯一,我们有可能需要为每个状态参数来安排一个递归出口,确保我们的递归能够确实有效的出去。

例如:
我们需要注意这里的递归出口:

当我们操作一棵树root的时候,我们的递归出口可能是if (root == null),

而我们在操作两颗树t1,t2的时候,我们的递归出口应该包括这两棵树所有为null的情况,如 if (t1 == null && t2 == null)和if (t1 == null || t2 == null)这样才能概况完所有为null的出口情况。

实例:
面试题 08.09. 括号
面试题 04.10. 检查子树

并且值得注意的是,我们的递归出口并不一定都是在最开头的位置,我们一般在最开头设置递归出口是希望递归能以最快的速度出去;
但是有时候我们在对当前节点进行一些相关处理操作之后我们就希望判断一下能不能递归出口,所以递归出口有可能是在代码中间的,大家需要灵活应用。

在这一步,我们需要思考题目需要的解在哪里?是在某一具体的深度、还是在叶子结点、还是在非叶子结点(包括根节点)、还是在每个节点、还是在从跟结点到叶子结点的路径

在某一具体的深度:if (depth >= n)
很常见,大部分就是

在每个节点:if (true)
面试题 08.04. 幂集

3. 明确所有路径

3.明确所有路径(选择):这个构思路径最好用树形图表示。
分支路径的选择由 for (int i; i < length; i++)中的 i 来决定的,list.add(i),即 选择第 i 条分支路径

例如:走迷宫有上下左右四个方向,也就是说我们站在一个点处有四种选择,我们可以画成无限向下延伸的四叉树。
直到向下延伸到叶子节点,那里便是出口;
从根节点到叶子节点沿途所经过的节点就是我们满足题目条件的选择。

操作:f(depth)

递归深度:方法参数 depth,递归出口if (depth >= n),递归深度增加f(depth + 1)

注意:递归深度depth,根节点深度为 0,一般根节点代表的解都为空解,所以深度为 0,之后的解深度为 1 才代表我们的第 1 次分支路径选择。

所有路径:for (int i; i < length; i++)即 枚举 i 所有可能的路径

路径分支选择:非方法参数,选择 for 循环中的分支路径 i,如 list.add(i);
如果不选那就不写list. add(i)就行了,没有 i,不用整些花里胡哨的。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwwszw.html