简介
二叉树的暴力递归方法其实就是对树形动态规划的一个暴力解决方法
。为什么说他暴力呢?主要是他的解题思路是通过不断的递归调用,分别把左树和右树收集到的信息进行整合,然后进行加工在返回给调用方
。对于许多笔试,甚至面试碰到树形DP问题都能很好的解决,虽然复杂度(时间和空间)
可能相对较高,后期可以自己换另外的方法来解决(例如使用迭代,或者自己压栈来代替递归操作)。
递归套路思想
首先我们先来看代码,这段代码主要是返回该树的叶子节点的个数。
public int getLefeNum(TreeNode root){
int num = 0;
// 非空校验,也是其中一个退出递归的条件
if(root == null){
return num;
}
// 收集左树的信息
int leftInfo = getLefeNum(root.left);
// 收集右树的信息
int rightInfo = getLefeNum(root.right);
// 判断信息的有效性
if(root.left == null && root.right == null){
num++;
}
// 处理信息并返回
return num + leftInfo + rightInfo;
}
上面这段代码还没有经过测试,但是大体思路肯定是这样的。首先,非空校验肯定是必须的,无论是刷题还是在写程序,避免后面使用该对象的时候会抛出空指针异常。
然后,我们看到核心代码:
// 收集左树的信息
int leftInfo = getLefeNum(root.left);
// 收集右树的信息
int rightInfo = getLefeNum(root.right);
第一个递归,收集这个节点左树的相关信息
。
第二个递归,收集这个节点右树的相关信息
。
这两个递归是解决方法的精髓。因为这个暴力递归是针对二叉树,而二叉树每个节点最多有两个子节点。所以,我们可以通过对收集该节点的左树信息进行递归,对收集该节点的右树信息进行递归。从而收集到整棵树的信息。
举个例子:
就拿上面那个收集叶子节点的代码来说,加入一颗两层二叉树(这里就用简单一点的树来举例),他的叶子节点就是2
,如下图:
那么,套上我们的递归:
1、进入程序,节点1不为空
2、开始节点1第一个递归
:
- 节点2也不为空,开始
节点2的第一个递归
- 此时,节点2是叶子节点,所以
节点2的第一个递归结束,返回0
- 同理,
节点2的第二个也会结束并返回0
- 此时,
节点2来到了if条件判断
- 因为其实叶子节点,所以
num++ 等于1
- 返回num以及
从左树收集到的信息:letInfo 和从右树收集到的信息 : rightInfi
的和,就是1+0+0 =1;
3、节点1第一个递归结束
,收集到左树的信息为1;
4、同理,对右树进行递归;节点1收集到右树的信息也是1
;
5、因为节点1不是叶子节点,所以直接返回num和左右树收集到的信息
。也就是0+1+1=2;
6、所以该树的叶子节点个数为2;
注意事项
递归方法的返回值可以根据需求自定义,可以是基本类型也可以是对象。总之,是你认为能够处理好问题的返回信息
即可。例如,判断一棵树是不是平衡二叉树,可以自定义一个对象类,类变量包括左树是否平衡,右树是否平衡,左树和右树的高度差值(或者左树和右树的高度差是不是小于1)
。总之,要收集的信息自己根据需求定义即可。
另外,不仅可以收集信息;也可以传递信息在收集(下面有leetcode原题)。但是中心思想不变:递归收集左右树信息,处理信息并返回
。
需要注意的是:避免错误的递归使虚拟机的出现栈异常
。
相关题目
剑指offer 68:两个节点的公共祖先:
class Solution {
// 自定义返回的收集信息
public class info{
boolean isLeft;
boolean isRight;
TreeNode pa;
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
info info = getInfo(root, p, q);
return info.pa;
}
// 递归方法
public info getInfo(TreeNode root, TreeNode p, TreeNode q) {
info f = new info();
if(root == null){
return f;
}
info infoL = getInfo(root.left, p, q);
info infoR = getInfo(root.right,p,q);
if(root.val == p.val || infoL.isLeft || infoR.isLeft){
f.isLeft = true;
}
if(root.val == q.val || infoL.isRight || infoR.isRight){
f.isRight = true;
}
if(infoL.pa != null || infoL !=null){
f.pa = infoL.pa == null? infoR.pa : infoL.pa;
}
if(f.isLeft && f.isRight && f.pa == null){
f.pa = root;
}
return f;
}
}
leetcode129目: 求根到叶子节点数字之和
class Solution {
public int sumNumbers(TreeNode root) {
if(root == null){
return 0;
}
int i = root.val;
return getInfo(root,0);
}
public int getInfo(TreeNode root,int x){
if(root.left == null && root.right == null){
return x * 10 + root.val;
}
int sum = x * 10 + root.val;
int leftInfo = 0,rightInfo = 0;
if(root.left != null){
leftInfo = getInfo(root.left,sum);
}
if(root.right != null){
rightInfo = getInfo(root.right,sum);
}
return leftInfo + rightInfo;
}
}
小结
过多的例题就不多说了,掌握思想不断练习即可。暴力递归也比较适合入门和笔试;至于面试,程序复杂度越低越好。