剑指Offer-41-平衡二叉树

  • Post author:
  • Post category:其他


题目

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

解析

预备知识

平衡二叉树:它是一棵空树,或者它的左右子树的高度差不超过1,同时它的左右子树为一棵二叉树。而衡量树和平衡因子说的就是左右子树的高度差,可以为0,1,-1。如下图它就是一棵平衡二叉树:

这里写图片描述

思路一


如何判断一棵树是否是平衡二叉树呢?

可以根据预备知识的定义出发,我们

只需确保树的左右子树的高度差不超过2即可

,同样它的左右子树也要满足这样的要求。原问题可以分解为子问题且处理逻辑一样,故可以使用递归来解决。

这里我们采用自底向上的做法,先判断左右子树是否是平衡二叉树,再判断左右子树合起来的子结构是否是平衡。这种先访问左右节点,最后根节点的顺序非常符合树的后序遍历,所以我们采用变形的递归的后序遍历算法。

而求节点的高度(深度)我们也采用同样的变形后序遍历来做,很容易想到一个树的深度为左子树和右子树深度的最大值 + 1。所以问题规模转变为求左子树和右子树的深度,问题形式不变,规模变小,典型的递归问题,最后向上回溯即可求得输入的根节点的高度。

    public int TreeDepth(TreeNode node) {
        if(node == null) {
            return 0;
        }
        int left = TreeDepth(node.left);
        int right = TreeDepth(node.right);
        return Math.max(left, right) + 1;
    }

    public boolean IsBalanced_Solution(TreeNode root) {
        if(root == null) {
            return true;
        }
        return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right)
                && Math.abs(TreeDepth(root.right) - TreeDepth(root.left)) < 2;
    }

思路二

思路一虽然实现简单,但存在非常重要的缺陷。就是在求节点的深度会重复遍历多个节点。比如上图中,我们求2节点的深度时,需要遍历2->4,而在求1的节点深度时,又要遍历1->2->4。

能不能在求节点的深度只需遍历一次节点,或者说我们在递归回溯时如何利用左右孩子的深度呢从而避免多次遍历。


我们这里就采用利用递归回溯时,上一次递归时得到的子树的高度来求出当前树结构的深度。其实思路一

TreeDepth

也利用此思想,但是它确实单独的模块,没有与判断平衡二叉树结合在一起。所以思路二主要是将思路一中的分开模块结合在一起。利用一个全局变量表示平衡标志位,在求树的深度时,对求得的左右子树的高度进行判断是否符合平衡二叉树的要求,若不满足,平衡标志位为false。并且在求树的深度的开头部分会判断这个标志位,若为false,则说明该树的某一部分不平衡,直接返回即可,表明这个树不是平衡的。

    static boolean isBalance = true;
    public static boolean IsBalanced_Solution2(TreeNode root) {
        if(root == null) {
            return true;
        }
        isBalance = true;
        caculateBalanceTree(root);
        return isBalance;
    }

    private static int caculateBalanceTree(TreeNode node) {
        if(node == null || !isBalance) {
            return 0;
        }
        int left = caculateBalanceTree(node.left);
        int right = caculateBalanceTree(node.right);
        isBalance = Math.abs(left - right) < 2 ? true : false;
        return Math.max(left, right) + 1;
    }

思路三

思路三的逻辑与思路二一致,都是考虑如何把求树的深度与判断树的平衡融合在一起。

思路二使用一个全局变量标志位。而思路三则无需全局变量,并且额外定义了一个深度持有类(原因是Java中没有指针,不能使用引用传递机制)。在c或c++中我们可以给递归的子过程中传入左右子树的高度的指针,当子过程回溯时,我们就可以拿到改变后的值,而这种效果得益于指针的优势。而在Java中只有值传递,子过程改变的值,在父过程中是看不见,子过程改变仅仅是实参的副本,实参本身是不发生变化的。所以我们要定义一对象来持有深度值,这样子过程通过引用修改对象的成员变量时,父过程中的引用由于与实参(该引用)的副本指向同一个对象,故而是可见的。

这里扯了很多Java的相关特性,C++大佬党请使用指针,谢谢。

public static class DepthHolder {
        int depth;
    }

    public static boolean IsBalanced_Solution3(TreeNode root) {
        if(root == null) {
            return true;
        }
        DepthHolder depthHolder = new DepthHolder();
        return isBalance(root, depthHolder);
    }

    private static boolean isBalance(TreeNode node, DepthHolder depthHolder) {
        if(node == null) {
            depthHolder.depth = 0;
            return true;
        }
        DepthHolder leftDepthHolder = new DepthHolder(), rightDepthHoder = new DepthHolder();
        if(isBalance(node.left, leftDepthHolder) && isBalance(node.right, rightDepthHoder)) {
            if(Math.abs(leftDepthHolder.depth - rightDepthHoder.depth) < 2) {
                depthHolder.depth = Math.max(leftDepthHolder.depth, rightDepthHoder.depth) + 1;
                return true;
            }
        }
        return false;
    }

总结

树的处理过程很多都可以与遍历方式结合,多多发掘吧。



版权声明:本文为dawn_after_dark原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。