递归
递归三要素:目的,停止条件,传递条件。
做递归思考三步:
-
递归的函数要干什么?
-
函数作用是判断传入的两个树是否镜像。
-
输入:TreeNode left, TreeNode right
-
输出:是:true,不是:false
-
递归停止的条件是什么?
-
左节点和右节点都为空 -> 倒底了都长得一样 ->true
-
左节点为空的时候右节点不为空,或反之 -> 长得不一样-> false
-
左右节点值不相等 -> 长得不一样 -> false
-
从某层到下一层的关系是什么?
-
要想两棵树镜像,那么一棵树左边的左边要和二棵树右边的右边镜像,一棵树左边的右边要和二棵树右边的左边镜像
-
调用递归函数传入左左和右右
-
调用递归函数传入左右和右左
-
只有左左和右右镜像且左右和右左镜像的时候,我们才能说这两棵树是镜像的
-
调用递归函数,我们想知道它的左右孩子是否镜像,传入的值是root的左孩子和右孩子。这之前记得判个root==null。
回溯
解决一个回溯问题,实际上就是一个决策树的遍历过程。你只需要思考 3 个问题:
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单。
写 backtrack 函数时,需要维护走过的「路径」和当前可以做的「选择列表」,当触发「结束条件」时,将「路径」记入结果集。
其实想想看,回溯算法和动态规划是不是有点像呢?我们在动态规划系列文章中多次强调,动态规划的三个需要明确的点就是「状态」「选择」和「base case」,是不是就对应着走过的「路径」,当前的「选择列表」和「结束条件」?
某种程度上说,动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table 或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划。而今天的两个问题,都没有重叠子问题,也就是回溯算法问题了,复杂度非常高是不可避免的。