A-二叉树的深度&数组中只出现一次的数字

本文探讨了二叉树的深度计算和平衡性判断算法,通过递归实现,并优化了平衡性判断过程。同时,介绍了如何利用异或运算找出数组中仅出现一次的两个数字,确保了时间和空间复杂度的要求。

题目39-1:输入一颗二叉树的根结点,求该树的深度。从根结点到叶结点依次经过的结点(包含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。二叉树定义如下。

    public class TreeNode{
        public int value;
        public TreeNode leftNode;
        public TreeNode rightNode;

        public TreeNode() {}

        public TreeNode(int value) {
            this.value = value;
        }
    }

分析:
  下图中的二叉树的深度为4,因为它从根结点到叶结点最长的路径包含4个结点(从根结点1开始,经过结点2和结点5,最终到达叶结点7)。
在这里插入图片描述
  ①如果一棵树只有一个结点,它的深度为1。
  ②如果根结点只有左子树而没有右子树,那么树的深度应该是其左子树的深度加1;同样如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1。
  ③如果既有右子树又有左子树,那该树的深度就是其左、右子树深度的较大值再加1。
  代码如下所示:

    public static int treeDepth(TreeNode pNode) {
        if (pNode == null) return 0;
        int nLeft = treeDepth(pNode.leftNode);
        int nRight = treeDepth(pNode.rightNode);
        return (nLeft > nRight) ? (nLeft + 1) : (nRight + 1);
    }

    public static void main(String[] args) {
        TreeNode node1 = new TreeNode(1);
        TreeNode node2 = new TreeNode(2);
        TreeNode node3 = new TreeNode(3);
        TreeNode node4 = new TreeNode(4);
        TreeNode node5 = new TreeNode(5);
        TreeNode node6 = new TreeNode(6);
        TreeNode node7 = new TreeNode(7);

        // 左右均有结点
//        node1.leftNode = node2;
//        node1.rightNode = node3;
//        node2.leftNode = node4;
//        node2.rightNode = node5;
//        node3.rightNode = node6;
//        node5.leftNode = node7;
//        System.out.println(treeDepth(node1));

        // 只有左结点
//        node1.leftNode = node2;
//        node2.leftNode = node3;
//        node3.leftNode = node4;
//        node4.leftNode = node5;
//        System.out.println(treeDepth(node1));

        // 只有右结点
//        node1.rightNode = node2;
//        node2.rightNode = node3;
//        node3.rightNode = node4;
//        node4.rightNode = node5;
//        System.out.println(treeDepth(node1));

        // 只有一个结点
        System.out.println(treeDepth(node1));
        // 结点为null
        System.out.println(treeDepth(null));
    }

题目39-2:输入一棵二叉树的根结点,判断该树是不是平衡二叉树。如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
分析:
  在遍历树的每个结点的时候,调用函数TreeDepth得到它的左右子树的深度。如果每个结点的左右子树的深度相差都不超过1,按照定义它就是一棵平衡的二叉树。
  代码如下所示:

    public static boolean isBalancedTree(TreeNode pNode) {
        if (pNode == null) return true;
        int left = treeDepth(pNode.leftNode);
        int right = treeDepth(pNode.rightNode);
        int dif = left - right;
        if (dif > 1 || dif < -1) return false;
        return isBalancedTree(pNode.leftNode) && isBalancedTree(pNode.rightNode);
    }

  注意到由于一个结点会被重复遍历多次,这种思路的时间效率不高。例如在isBalancedTree方法中输入上图中的二叉树,我们将首先判断根结点(结点1)是不是平衡的。此时我们往函数TreeDepth输入左子树的根结点(结点2)时,需要遍历结点4、5、7。接下来判断以结点2为根结点的子树是不是平衡树的时候,仍然会遍历结点4、5、7。毫无疑问,重复遍历同一个结点会影响性能。
  换个角度来思考,如果我们用后序遍历的方式遍历二叉树的每一个结点,在遍历到一个结点之前我们就已经遍历了它的左右子树。只要在遍历每个结点的时候记录它的深度(某一结点的深度等于它到叶节点的路径的长度),我们就可以一边遍历一边判断每个结点是不是平衡的。
  代码如下所示:

   static class Tuple {
        private boolean isBalanced;
        private int depth;

        public Tuple() {
        }

        public Tuple(boolean isBalanced, int depth) {
            super();
            this.isBalanced = isBalanced;
            this.depth = depth;
        }
		// 省略get、set方法
    }
    
	public static boolean isBalancedTree(TreeNode pNode) {
        Tuple tuple = isBalanced(pNode);
        return tuple.isBalanced();
    }

    public static Tuple isBalanced(TreeNode pNode) {
        if (pNode == null) {
            Tuple tuple = new Tuple();
            tuple.setBalanced(true);
            tuple.setDepth(0);
            return tuple;
        }
        Tuple left = isBalanced(pNode.leftNode);
        Tuple right = isBalanced(pNode.rightNode);
        if (left.isBalanced() && right.isBalanced()) {
            int dif = left.getDepth() - right.getDepth();
            if (dif >= -1 && dif <= 1) {
                return new Tuple(true, (left.getDepth() > right.depth ? left.getDepth() : right.getDepth()) + 1);
            }
        }
        return new Tuple(false, -1);
    }

    public static void main(String[] args) {
        TreeNode node1 = new TreeNode(1);
        TreeNode node2 = new TreeNode(2);
        TreeNode node3 = new TreeNode(3);
        TreeNode node4 = new TreeNode(4);
        TreeNode node5 = new TreeNode(5);
        TreeNode node6 = new TreeNode(6);
        TreeNode node7 = new TreeNode(7);

        // 平衡二叉树
//        node1.leftNode = node2;
//        node1.rightNode = node3;
//        node2.leftNode = node4;
//        node2.rightNode = node5;
//        node3.rightNode = node6;
//        node5.leftNode = node7;
//        System.out.println(isBalancedTree(node1));

        // 不是平衡的二叉树
//        node1.leftNode = node2;
//        node1.rightNode = node3;
//        node2.leftNode = node4;
//        node2.rightNode = node5;
//        node5.rightNode = node6;
//        node6.leftNode = node7;
//        System.out.println(isBalancedTree(node1));

        // 只有左结点
//        node1.leftNode = node2;
//        node2.leftNode = node3;
//        node3.leftNode = node4;
//        node4.leftNode = node5;
//        System.out.println(isBalancedTree(node1));

        // 只有右结点
//        node1.rightNode = node2;
//        node2.rightNode = node3;
//        node3.rightNode = node4;
//        node4.rightNode = node5;
//        System.out.println(isBalancedTree(node1));

        // 只有一个结点
        System.out.println(isBalancedTree(node1));
        // 结点为null
        System.out.println(isBalancedTree(null));
    }

题目40:一个整型数组里除了两个数字之外,其它的数字都出现了两次。请写出程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
分析:
  这个问题可以可以使用用异或的性质解决。异或的性质:对于整数a,有:
  (1)a^a=0
  (2)a^0=a
  (3)a^b^c=a^(b^c)=(a^c)^b
  问题的思路如下:
  (1)对于出现两次的元素,使用“异或”操作后结果肯定为0,那么我们就可以遍历一遍数组,对所有元素使用异或操作,那么得到的结果就是两个出现一次的元素的异或结果。
  (2)因为这两个元素不相等,所以异或的结果肯定不是0,也就是可以在异或的结果中找到1位不为0的位的位置,记为第n位。
  (3)这样就可以用第n位是不是1为标准,将原数组分成两个子数组。一组该位全为1,另一组该位全为0。出现两次的数字肯定会被分到同一个子数组中,且每个数组包含一个只出现一次的数字。
  (4)分别对两个子数组求异或,就能找出只出现一次的数字。

    public static int[] findNumsAppearOnce(int[] array) {
        if (array == null || array.length < 2) return array;
        int[] result = new int[2];
        int resultExclusiveOR = array[0];
        for (int i = 1; i < array.length; i++) {
            resultExclusiveOR ^= array[i];
        }
        int bitIndex = 0;
        for (int i = 0; i < 32; i++) {  //找出异或结果为1的位。
            if ((resultExclusiveOR >> i & 1) == 1) {
                bitIndex = i;
                break;
            }
        }
        for (int i = 0; i < array.length; i++) { //根据bitIndex为1,将元素分为两组
            if ((array[i] >> bitIndex & 1) == 1)
                result[0] ^= array[i];   //对应位为1,亦或得到的结果
            else
                result[1] ^= array[i];   //对应位为0,亦或得到的结果
        }
        return result;
    }


    public static void main(String[] args) {
//        int[] numberAppearOnce = findNumsAppearOnce(new int[]{1, 2, 3, 4, 2, 4, 5, 5});
        // 不通过
        int[] numberAppearOnce = findNumsAppearOnce(new int[]{1, 2, 3, 4, 5, 6, 7, 8});
//        int[] numberAppearOnce = findNumsAppearOnce(new int[]{1});
//        int[] numberAppearOnce = findNumsAppearOnce(new int[]{});
        for (int i = 0; i < numberAppearOnce.length; i++) {
            System.out.print(numberAppearOnce[i] + " ");
        }
    }

  时间复杂度:O(n),空间复杂度:常数。
  也可使用一个Map,Map对应的键值key就是数组中的元素,value就是这个元素出现的次数。这样我通过一次遍历数组中的元素,如果元素出现在map中,则将其对应的value加1,否则将元素添加到map中,这样遍历一遍数组,我们就可以得到数组中每个元素对应出现的次数,然后再通过遍历一遍map,返回value为1对应的key就是我们需要求得元素。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值