Java——二叉树递归遍历综合练习(Leecode104/111/226/100/101/222/110/112/404)

简单回顾二叉树的递归遍历应用

今天我们来一起来看一下二叉树的的递归遍历以及其他的相关应用。首先回顾一下二叉树的前序遍历。

public class TreeNode {
     int val;
     TreeNode left;
     TreeNode right;
     TreeNode(int x) { val = x; }
 }
Class Solution{
	List<Integer> list = new ArrayList<Integer>();
	public List<Integer> preorderTravel(TreeNode root){
		if(root!=null){                             //1
			list.add(root.val);                     //2
			preorderTravel(root.left);              //3
			preorderTravel(root.rigth);             //4
		}
		return list;
	}
}

为了更好地理解递归的终止条件,我们将1234这一部分代码改为如下:

if(root == null)
	return list;
list.add(root.val);      
preorderTravel(root.left);       
preorderTravel(root.rigth);

在这部分代码中,我们将递归代码分为两部分。1.终止条件2.递归过程。所以在写关于二叉树的递归算法的时候我们可以从宏观来把握,将整棵树抽象成成根、左边一个节点、右边一个节点。然后对这种抽象后的树进行归算法的描述,一定注意结束条件的选取。若没有结束条件,程序将一直执行,内存溢出。
根据上面的描述,我们试一下在一棵树中查找是否存在节点key(伪代码)

boolean containKey(TreeNode root, int key){
	if(root == null) return false;
	if(root.val == key) return true;
	if(containKey(root.left,key)||containKey(root.rigth,key))
		return true;
	return false;
}

抽象后来看,若一进来根节点为空,不可能存在节点等于key返回false。若根节点等于key,那么就存在该节点返回true就可以啦。若既不为空也不是当前节点,那么去判断,左子树或者右子树含有当前节点就返回true。若左右自述都没有返回false即可。
思路是不是很简单,利用这种思路来看几个编程练习。

Leecode104 计算树的深度:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) //根节点为空,树高为0
            return 0;
        int leftdepth = maxDepth(root.left);//计算左子树高度
        int rightdepth = maxDepth(root.right);//计算右子树高度
        //返回左子树和右子树中高度大的那一个,但不要忘记要计算当前节点高度,所以在左右子树高度基础上要+1
        return Math.max(leftdepth+1,rightdepth+1);
    }
}

Leecode111 二叉树的最小深度

那么我们按照原来的思路进行:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) return 0;       
        int minleft = minDepth(root.left);
        int minright = minDepth(root.right);
        return Math.min(minleft,minright)+1;
    }
}

看起来似乎没什么问题,最大该最小嘛。但是如果这样提交会出现问题:
在这里插入图片描述
这是为什么呢。计算树的高度,从底向上取最大值,直到最后没问题。但是最小值会有这样的问题,测试用例为12,代表根节点为1,做孩子为2,右节点为空。此时计算最小,算出来的根节点做孩子为1,右孩子为0。取最小+1,所以根节点的最低高度变为1。但是我们知道最小深度不能计算其右节点啦,只能计算其左子树的高度。
那我们改一下,判断一下左右子树是否为空是不是就可以了呢:

public int minDepth(TreeNode root) {
        if(root == null) return 0; 
        if(root.left!=null)      
            int minleft = minDepth(root.left);
        if(root.right!=null)
            int minright = minDepth(root.right);
        return Math.min(minleft,minright)+1;
    }

这里需要考虑,这样定义minleft和minrigth是否合理。如果一棵树只有根节点,直接返回Math.min(minleft,minrigth)+1,这两个变量是啥?机器蒙掉了,所以不能这么定义变量啦。
那么我们把变量定义在两个if语句外面,值定义一个最小值,用最小值接受左子树和右子树的值。那如果同时用min接受,min会被一直覆盖,没有办法判断最小值到底是谁。所以我们定义min后取无穷,只有当左右子树返回的值比当前min小我们才更新。最后返回min+1即可。
又改完了,那现在OK了吗~~测试一下,如果只有一个根节点,min=最大值啦,if不执行直接返回min+1,发生了什么?明明高度为1,却返回了最大值+1。

public int minDepth(TreeNode root) {
        if(root == null) return 0; 
        
        int min = Integer.MAX_VALUE;
        if(root.left!=null)      
            min = Math.min(minDepth(root.left),min);
        if(root.right!=null)
           min = Math.min(minDepth(root.right),min);
        return min+1;
    }

所以在检查一下结束条件和逻辑,好像漏掉了左右都为空(其实也就是叶子结点)的情况。那么叶子结点的高度我们应该返回1.所以最终正确代码如下:

class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) return 0; 
        if(root.left==null&&root.right==null)
            return 1;
        int min = Integer.MAX_VALUE;
        if(root.left!=null)      
            min = Math.min(minDepth(root.left),min);
        if(root.right!=null)
           min = Math.min(minDepth(root.right),min);
        return min+1;
    }
}

Leecode226 翻转二叉树

没什么可多说的了,类比交换两个数据即可。

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if(root == null) return null;
        
        TreeNode tempnode = root.left;
        root.left = root.right;
        root.right = tempnode;

        invertTree(root.left);
        invertTree(root.right);
        return root;
    }
}

插播一下这个题目的非递归方法:

 public TreeNode invertTree(TreeNode root) {
        if(root==null) return null;
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()){
            TreeNode cur = queue.poll();
            TreeNode temp = cur.left;
            cur.left = cur.right;
            cur.right = temp;
            if(cur.left!=null)
                queue.offer(cur.left);
            if(cur.right!=null)
                queue.offer(cur.right);
        }
        
        return root;
    }

Leecode100 判断两棵树是否相同

递归:

 public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p==null&&q==null) return true;
        if(p==null&&q!=null||p!=null&&q==null) return false;
        if(p.val!=q.val) return false;
        if(isSameTree(p.left,q.left)&&isSameTree(q.right,p.right))
            return true;
        return false;
    }

非递归:

public boolean check(TreeNode p, TreeNode q) {
    // p and q are null
    if (p == null && q == null) return true;
    // one of p and q is null
    if (q == null || p == null) return false;
    if (p.val != q.val) return false;
    return true;
  }

  public boolean isSameTree(TreeNode p, TreeNode q) {
    if (p == null && q == null) return true;
    if (!check(p, q)) return false;

    // init deques
    ArrayDeque<TreeNode> deqP = new ArrayDeque<TreeNode>();
    ArrayDeque<TreeNode> deqQ = new ArrayDeque<TreeNode>();
    deqP.addLast(p);
    deqQ.addLast(q);

    while (!deqP.isEmpty()) {
      p = deqP.removeFirst();
      q = deqQ.removeFirst();

      if (!check(p, q)) return false;
      if (p != null) {
        // in Java nulls are not allowed in Deque
        if (!check(p.left, q.left)) return false;
        //都为空,或者都不为空且相等执行下一个if
        if (p.left != null) {//两棵树的左子树相同,且都不为空
          deqP.addLast(p.left);
          deqQ.addLast(q.left);
        }
        if (!check(p.right, q.right)) return false;
        if (p.right != null) {//两棵树的右子树相同,且都不为空
          deqP.addLast(p.right);
          deqQ.addLast(q.right);
        }
      }
    }
    return true;
  }

Leecode101 是否为对称二叉树

在这里插入图片描述
将一棵树从根部分为左右两部分,判断左右两部分是否为镜像。判断左子树的左孩子与右子树的右孩子是否相等,左子树的右孩子跟右子树的左孩子是否相等。
递归:

public boolean isSymmetric(TreeNode root){
        return isMirror(root,root);
    }
    public boolean isMirror(TreeNode root1, TreeNode root2){
        if(root1==null && root2==null) return true;
        if(root1==null||root2==null) return false;
        return root1.val==root2.val&&isMirror(root1.left,root2.right)&&isMirror(root1.right,root2.left);
    }

非递归:

 public boolean isSymmetric(TreeNode root){
       if(root==null) return true;
       Queue<TreeNode> que = new LinkedList<TreeNode>();
       que.offer(root);
       que.offer(root);
       while(!que.isEmpty()){
           TreeNode t1 = que.poll();
           TreeNode t2 = que.poll();
           if(t1==null&&t2==null) continue;
           if(t1==null||t2==null) return false;
           if(t1.val!=t2.val) return false;
           que.offer(t1.left);
           que.offer(t2.right);
           que.offer(t1.right);
           que.offer(t2.left);
       }
       return true;
    }

Leecode222 完全二叉树节点个数

提供两种递归方式,一种自下向上返回个数(左右子树总和+1),一种自上向下边遍历边计算节点个数。

class Solution {
   //从下向上计算
  public int countNodes(TreeNode root) {
        if(root == null) return 0;
        return countNodes(root.left)+countNodes(root.right)+1;
    }
}
class Solution {
    int count = 0;//边遍历边计算节点个数
    public int countNodes(TreeNode root) {
        if(root==null) return 0;
        count++;
        countNodes(root.left);
        countNodes(root.right);
        return count;
    }
}

Leecode110 平衡二叉树判断

递归:
①自顶向下判断,判断当前根节点是否左右子树平衡。平衡后再计算左子树的左右孩子是否平衡。每次判断过程中都需要计算孩子的左右子树高度,其出现大量重复计算。

class Solution {
//从上向下计算
    public boolean isBalanced(TreeNode root) {
        if(root==null) return true;
        if(Math.abs(deep(root.left)-deep(root.right))>1) return false;
        return isBalanced(root.left)&&isBalanced(root.right);
    }
    public int deep(TreeNode node){
        if(node == null) return 0;
        **int deepleft = deep(node.left);
        int deeprigth = deep(node.right);
        return Math.max(deepleft+1,deeprigth+1);
        //return Math.max(deepleft,deeprigth)+1;
    }
}

注:最后两种返回方式差别很大,第一种效率99,第二种效率17
②改善自顶向下计算冗余,采用自底向上方式计算树的高度。需要定义额外的数据结构存储高度和真假值。

 public class TreeInfo{
     int height;
     boolean isbanlance;
     TreeInfo(int height,boolean isbanlance){
         this.height = height;
         this.isbanlance = isbanlance;
     }
 }
class Solution {
    public boolean isBalanced(TreeNode root) {
       return Judge(root).isbanlance;
    }
    public TreeInfo Judge(TreeNode root){
        if(root==null) return new TreeInfo(-1,true);
        TreeInfo left = Judge(root.left);
        if(!left.isbanlance)
            return new TreeInfo(-1,false);
        TreeInfo right = Judge(root.right);
        if(!right.isbanlance)
            return new TreeInfo(-1,false);**
        if(Math.abs(left.height-right.height)<2)
            return new TreeInfo(Math.max(left.height,right.height)+1,true);
        return new TreeInfo(-1,false);
    }
}

Leecode112 路径总和为sum

在这里插入图片描述

class Solution {
    public boolean hasPathSum(TreeNode root, int sum) {
        if(root==null) return false;
        if(root.left==null&&root.right==null&&root.val==sum)
            return true;
        if(hasPathSum(root.left,sum-root.val)||hasPathSum(root.right,sum-root.val))
            return true;
        return false;
    }
}

这个题目中要注意返回条件,如果不对左右子树进行空的判断就不能保证其为叶子结点,就可能出现错误。如[5,null,1,null,null,4]sum=5.此时会返回true,因为根节点向右遍历的时候满足==5.但是这并不是根节点,所以要注意结束条件的判断。

Leecode404 二叉树左叶子的和

class Solution {
    int sum = 0;
    public int sumOfLeftLeaves(TreeNode root) {
        getSum(root,false);
        return sum;
    }
    public void getSum(TreeNode root,boolean flag){
        if(root!=null){
            if(root.left==null&&root.right==null&&flag)
                sum+=root.val;
            getSum(root.left,true);
            getSum(root.right,false);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值