LeetCode110. 平衡二叉树:自顶向下+记忆化+自底向上(超详细的解析!!!!)

https://leetcode-cn.com/problems/balanced-binary-tree/

题意

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

示例 1:
在这里插入图片描述

输入:root = [3,9,20,null,null,15,7]
输出:true

示例 2:
在这里插入图片描述

输入:root = [1,2,2,3,3,null,null,4,4]
输出:false

示例 3:

输入:root = [1,2,2,3,3,null,null,4,4]
输出:false

题解

解法一:自顶向下

这道题希望我们判断一棵树是否是平衡二叉树,即所有节点的左右子树高度差不能超过1。既然判断的条件是高度差不能超过1,那么我们势必要算出左右子树的高度,那么如何求一个节点的高度?当前节点的高度与左右子树有关,那么可以使用递归方法求解,如下。

//获取高度
private int height(TreeNode root){
    if(root==null)return 0;
    return 1+Math.max(height(root.left),height(root.right));
}

要判断一棵树是否是平衡二叉树,那么我们只要对每一个节点判断是否平衡即可,至此自然就想到了遍历。通过遍历判断每一个节点是否平衡,不平衡就停止遍历输出false这就是解题思路。那么使用哪一种遍历呢?其实使用哪一种遍历都是一样的,因为我们对每一个节点都需要进行判断,哪里可能先失衡是难以预料的。

class Solution {
   public boolean isBalanced(TreeNode root) {
       if(root==null)return true;
       return isBalanced(root.left)&&isBalanced(root.right)&&Math.abs(height(root.left)-height(root.right))<2;
   }
   //获取高度
   private int height(TreeNode root){
       if(root==null)return 0;
       return 1+Math.max(height(root.left),height(root.right));
   }
}

事实上,这种方法存在着大量的重复计算,而问题就在于height方法对于高度的获取。假如我们使用先序遍历对树进行访问,那么如下所示,当我们访问节点1,想要判断它是否失衡时,我们必须获取它的左子树与右子树的高度。
在这里插入图片描述
于是获取节点2与节点3的高度。但节点2与3的高度获取,同样也取决于他们的左右子树。
在这里插入图片描述
于是再获取左右子树高度。
在这里插入图片描述
然而还是同样的问题,还是要先获取子树高度。
在这里插入图片描述
此时除了根节点1之外的节点高度都已经被计算过了,终于可以判断头节点是否失衡,但发现没有失衡,于是按先序遍历的顺序进入左节点判断。
在这里插入图片描述
此时判断节点2是否失衡,还是要获取节点4、5的高度。
在这里插入图片描述
节点4高度的获取还是取决于其左右节点8、9的高度,于是计算8、9的高度。
在这里插入图片描述
此时终于可以判断节点2是否失衡,但相信大家都发现了,之前为了判断根节点1是否失衡,将所有子节点的高度都计算了一遍,而在后续子节点的判断中,仍然需要计算大量之前已经计算过的节点,也就是存在着大量的重复计算,假想一下这是一棵非常庞大的树,那么这样重复的计算也是非常可怕的。或许将之前的遍历改为后续遍历可以修正这个问题?但哪怕改为后续遍历,也只是先判断节点2和3,然后再判断节点1,而高度的计算是在height方法中并没有改变,为了判断节点2所计算过的子节点,在节点1中还是会重复计算。

解法二:记忆化

既然方法一中需要重复计算之前已经计算过的节点,造成了重复计算,那么我们能不能将已经计算过的节点高度存起来,当需要再次使用时我们不进行计算,而是直接使用之前计算的结果呢?显然是可以的,但问题是如何存储每一个节点的高度?也是需要有一个办法将每一个节点区分开。在此我们可以回想一下完全二叉树的性质,当我们将根节点编号编号为1时,其他节点的编号如下图所示。
在这里插入图片描述
虽然题目中的树并不是完全二叉树,但我们仍然可以使用这个方法给每个节点编号,这样不同的节点就可以得到不同的编号了(左节点的编号是父节点的两倍,右节点的编号是父节点的两倍加一),然后我们再将每个计算过的节点高度存入HashMap中,需要使用时再取出即可。

class Solution {
    HashMap<Integer,Integer> map=new HashMap<>();

    public boolean isBalanced(TreeNode root) {
        return traversal(root,1);
    }

    private boolean traversal(TreeNode root,int num){
        if(root==null)return true;
        return traversal(root.left,num*2)&&traversal(root.right,num*2+1)&&Math.abs(height(root.left,num*2)-height(root.right,num*2+1))<2;
    }
    private int height(TreeNode node,int num){
        if(node==null)return 0;
        if(map.containsKey(num))
            return map.get(num);
        int l=height(node.left,num*2);
        int r=height(node.right,num*2+1);
        map.put(num,1+Math.max(l,r));
        return map.get(num);
    }
}

解法三:自底向下

事实上,我们在调用height方法获取节点高度时,已经对其所有子节点进行了一次遍历,计算节点高度,并将每个子节点的高度返回供父节点使用,只不过height方法仅仅计算了高度,却没有判断每个节点是否平衡,所以才画蛇添足又进行了一次遍历来进行判断,因此我们需要对此进行修改,而height方法的返回值是int类型,如果一个节点失衡了,我们可以返回一个-1来代表失衡,并提前结束遍历。

 class Solution {
      public boolean isBalanced(TreeNode root) {
          return getHeight(root)!=-1;
      }
      private int getHeight(TreeNode root){
          if(null==root)return 0;
          int leftHeight=getHeight(root.left);
          int rightHeight=getHeight(root.right);
          if(leftHeight==-1||rightHeight==-1||Math.abs(leftHeight-rightHeight)>1)
              return -1;
         return 1+Math.max(leftHeight,rightHeight);
      }
 }

让我们简单看看这段代码进行了什么操作。
后续遍历的顺序,先计算节点8的高度为1,然后返回给父节点4,并进入右节点。
在这里插入图片描述
计算节点9的高度,然后返回给父节点4,再计算父节点。
在这里插入图片描述

有了两个子节点高度,父节点的高度以及是否失衡也就可以知道了。
在这里插入图片描述
此外还可以对代码进行优化,上面的代码先判断了左右节点是否失衡,然后再判断自身,但其实当左节点失衡的时候,已经没有必要再计算右节点了,应该直接返回-1,优化代码如下。

class Solution {
   public boolean isBalanced(TreeNode root) {
        return getHeight(root)!=-1;
    }

    private int getHeight(TreeNode root){
        if(null==root)return 0;
        int leftHeight,rightHeight;
        if((leftHeight=getHeight(root.left))==-1
                ||(rightHeight=getHeight(root.right))==-1
                ||Math.abs(leftHeight-rightHeight)>1)
            return -1;
       return 1+Math.max(leftHeight,rightHeight);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值