数据结构和算法十九

本文介绍了两种判断平衡二叉树的方法:后序遍历+剪枝以及先序遍历+判断深度,并分析了它们的时间复杂度。同时,针对求1+2+...+n的问题,利用逻辑运算符的短路效应和移位操作避免使用禁止的关键字,提出了两种创新解法。
摘要由CSDN通过智能技术生成

剑指 Offer 55 - II. 平衡二叉树

题目:输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。

示例 1:
         给定二叉树 [3,9,20,null,null,15,7]

                3
               / \
              9  20
                /  \
               15   7

         返回 true 。
示例 2:
         给定二叉树 [1,2,2,3,3,null,null,4,4]

                   1
                  / \
                 2   2
                / \
               3   3
              / \
             4   4

         返回 false 。
限制:
         0 <= 树的结点个数 <= 10000

此题为面试题55 - I. 二叉树的深度的拓展,建议先做上一题。

以下两种方法均基于以下性质推出:此树的深度等于左子树的深度右子树的深度中的最大值+1。
在这里插入图片描述
方法一:后序遍历 + 剪枝 (从底至顶)

此方法为本题的最优解法,但剪枝的方法不易第一时间想到。

思路是对二叉树做后序遍历,从底至顶返回子树深度,若判定某子树不是平衡树则 “剪枝” ,直接向上返回。

算法流程

recur(root) 函数:

  • 返回值:
    当节点root 左 / 右子树的深度差 ≤1 :则返回当前子树的深度,即节点 root 的左 / 右子树的深度最大值 +1 ( max(left, right) + 1 );
    当节点root 左 / 右子树的深度差 > 2:则返回 -1,代表 此子树不是平衡树 。
  • 终止条件:
    当 root 为空:说明越过叶节点,因此返回高度 0 ;
    当左(右)子树深度为 -1:代表此树的 左(右)子树 不是平衡树,因此剪枝,直接返回 -1

isBalanced(root) 函数:

  • 返回值:若 recur(root) != -1,则说明此树平衡,返回true;否则返回false 。

复杂度分析:
时间复杂度 O(N): N 为树的节点数;最差情况下,需要递归遍历树的所有节点。
空间复杂度 O(N): 最差情况下(树退化为链表时),系统递归需要使用O(N) 的栈空间。这里是引用

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

    private int recur(TreeNode root) {
        if (root == null) return 0;
        int left = recur(root.left);
        if(left == -1) return -1;
        int right = recur(root.right);
        if(right == -1) return -1;
        return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1;
    }
}

方法二:先序遍历 + 判断深度 (从顶至底)

此方法容易想到,但会产生大量重复计算,时间复杂度较高。

思路是构造一个获取当前子树的深度的函数 depth(root) (即 面试题55 - I. 二叉树的深度 ),通过比较某子树的左右子树的深度差 abs(depth(root.left) - depth(root.right)) <= 1 是否成立,来判断某子树是否是二叉平衡树。若所有子树都平衡,则此树平衡。

算法流程

isBalanced(root) 函数: 判断树 root 是否平衡

  • 特例处理: 若树根节点 root 为空,则直接返回 truetrue ;
  • 返回值: 所有子树都需要满足平衡树性质,因此以下三者使用与逻辑 && 连接;
    1、abs(self.depth(root.left) - self.depth(root.right)) <= 1 :判断 当前子树 是否是平衡树;
    2、self.isBalanced(root.left) : 先序遍历递归,判断 当前子树的左子树 是否是平衡树;
    3、self.isBalanced(root.right) : 先序遍历递归,判断 当前子树的右子树 是否是平衡树;

depth(root) 函数: 计算树 root 的深度

  • 终止条件: 当 root 为空,即越过叶子节点,则返回高度0 ;
  • 返回值: 返回左 / 右子树的深度的最大值+1 。

复杂度分析:

  • 时间复杂度 O(N log N):最差情况下(为 “满二叉树” 时), isBalanced(root) 遍历树所有节点,判断每个节点的深度 depth(root) 需要遍历 各子树的所有节点 。
    1、满二叉树高度的复杂度 O(log N),将满二叉树按层分为 log (N+1) 层;
    2、通过调用 depth(root) ,判断二叉树各层的节点的对应子树的深度,需遍历节点数量为N×1, [(N−1)/2]×2,[(N−3)/4]×4, [(N−7)/8]×8,…,1× (N+1)/2 。因此各层执行 depth(root) 的时间复杂度为O(N) (每层开始,最多遍历N个节点,最少遍历(N+1)/2 个节点)。
    其中,[(N−3)/4]×4代表从此层开始总共需遍历N−3 个节点,此层共有 4 个节点,因此每个子树需遍历 (N−3)/4个节点。
    3、因此,总体时间复杂度=每层执行复杂度 × 层数复杂度 = O(N×logN) 。
    在这里插入图片描述
  • 空间复杂度 O(N):最差情况下(树退化为链表时),系统递归需要使用 O(N) 的栈空间。
class Method2{
    public boolean isBalanced(TreeNode root) {
        if (root == null) return true;
        return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
    }

    private int depth(TreeNode root) {
        if (root == null) return 0;
        return Math.max(depth(root.left), depth(root.right)) + 1;
    }
}

剑指 Offer 64. 求 1 + 2 + … + n

题目:求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
示例 1:
         输入: n = 3
         输出: 6
示例 2:
         输入: n = 9
         输出: 45
限制:
         1 <= n <= 10000

解题思路:

本题在简单问题上做了许多限制,需要使用排除法一步步导向答案。
1+2+…+(n−1)+n 的计算方法主要有三种:平均计算、迭代、递归。

方法一: 平均计算
问题: 此计算必须使用 乘除法 ,因此本方法不可取,直接排除。

public int sumNums(int n) {
    return (1 + n) * n / 2;
}

方法二: 迭代
问题: 循环必须使用 while 或 for,因此本方法不可取,直接排除。

public int sumNums(int n) {
    int res = 0;
    for(int i = 1; i <= n; i++)
        res += i;
    return res;
}

方法三: 递归
问题: 终止条件需要使用 if ,因此本方法不可取。

public int sumNums(int n) {
    if(n == 1) return 1;
    n += sumNums(n - 1);
    return n;
}

在这里插入图片描述

逻辑运算符的短路效应:
常见的逻辑运算符有三种,即 “与 &&”,“或 ||”,“非 ! ” ;而其有重要的短路效应,如下所示:
if(A && B) // 若 A 为 false ,则 B 的判断不会执行(即短路),直接判定 A && B 为 false
if(A || B) // 若 A 为 true ,则 B 的判断不会执行(即短路),直接判定 A || B 为 true
本题需要实现 “当 n = 1n=1 时终止递归” 的需求,可通过短路效应实现。
n > 1 && sumNums(n - 1) // 当 n = 1 时 n > 1 不成立 ,此时 “短路” ,终止后续递归。

复杂度分析:
时间复杂度 O(n): 计算 n+(n−1)+…+2+1 需要开启 n 个递归函数。
空间复杂度 O(n): 递归深度达到 n ,系统使用 O(n) 大小的额外空间。

class Method{
    int res = 0;
    public int sumNums(int n) {
        boolean x = n > 1 && sumNums(n - 1) > 0;
        res += n;
        return res;
    }
}
//或
class Method{
    public int sumNums(int n) {
        boolean x = n > 1 && (n += sumNums(n - 1)) > 0;
        return n;
    }
}

方法四:移位

n在[1,10000]区间之内,n 二进制展开最多不会超过 14位,手动展开14层代替循环即可。

复杂度分析
时间复杂度:O(logn)。快速乘需要的时间复杂度为O(logn)。
空间复杂度:O(1)。只需要常数空间存放若干变量。

class Method{
    public int sumNums(int n) {
        int ans = 0, A = n, B = n + 1;
        boolean flag;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        flag = ((B & 1) > 0) && (ans += A) > 0;
        A <<= 1;
        B >>= 1;

        return ans >> 1;
    }
}

总结:

       不知不觉,这已经是我写的第二十一篇博客了,特别的感谢各位小伙伴的关注和点赞,这些天也让我收获了很多!我也想明白了一些事!
       我深感自身的不足,我还需要继续努力的深造!大城市的要求高,待遇好!可需要抗压的能力以及消费水平都比二三线城市要高很多!我深刻的感受到想要在大城市站住脚跟除了有学历的支撑还要有工作经验的支撑!
       这个五一让我收获颇丰,想想之前的压力放到现在根本就不值得一提,在以后的学习中还需要更加努力!等剑指Offer算法更新完了以后,就开始真正的更新Java生态体系的知识点!也重新让自己的大脑过一遍Java。
       最后,愿我们都能在各行各业中能够取得不同的成就!能够用自身的所学知识为国家贡献出自己的一份力量!一起加油!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值