算法题解记录16+++二叉树的直径(百日筑基)

文章讲述了如何解决二叉树直径问题,从最初误解问题到逐步分析,涉及到递归求解、中序遍历、重复计算的优化,以及如何通过直接比较子树深度来减少计算量。
摘要由CSDN通过智能技术生成

        其实题主本人看到这个题是有点懵的。

        二叉树的直径?又是简单题,不就是左子树深度+右子树深度就行了?【在此,定义根节点深度为0】

        写了半天,奇怪,计算最大深度我是会了,左右子树最大深度怎么算?

        接着总算想起用两个变量存储左右子树深度,提交答案,怎么莫名出错?

        随后注意到:树的直径不一定从根节点通过,这才大悟,可是更懵了,不从根节点通过,一个随机节点怎么找?该怎么解决?

        后来也是看了题解,理解了一遍。

题目描述:

        给你一棵二叉树的根节点,返回该树的 直径 。

        二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。

        两节点之间路径的 长度 由它们之间边数表示。

示例 1:

输入:root = [1,2,3,4,5]
输出:3
解释:3 ,取路径 [4,2,1,3] 或 [5,2,1,3] 的长度。

示例 2:

输入:root = [1,2]
输出:1

提示:

  • 树中节点数目在范围 [1, 104] 内
  • -100 <= Node.val <= 100

解题准备:

        1.了解二叉树:二叉树由根节点、左子树、右子树组成。

        2.理解题意:题目求二叉树直径,如果是随机节点,编码起来必然千难万难,既需要考虑节点root,又得考虑root.left,root.right等等,如果得到了经过root节点的直径,又得拿左右子节点的直径,此时问题非常复杂,且难以划分。

解题难点1分析:

        正如上述:随机节点难以解决,因此需要将其转化为“非随机”节点。

        从系统的角度分析,我们希望输入、处理流程可以固定。

        化简:假设直径必然经过根节点

                如题,如果直径必然经过根节点,该如何处理?

                由于经过根节点,所以假设根节点与左子树的最大距离是L,与右子树是R,那么,简化后,直径是L+R。

                重点就转化为:求左子树、右子树的最大深度。

                        再化简,转为求树root的最大深度:root的最大深度,就是左子树最大深度(或者右子树最大深度)+1,如此递归下去,直到得到结果。

                        伪代码为:

// 伪代码
public int maxDepth(TreeNode root){
    if(root==null){
        return 0; // 空树的最大深度为0
    }
    int lD=maxDepth(root.left); // 左子树最大深度
    int rD=maxDepth(root.right); // 右子树最大深度

    return Math.max(lD, rD)+1; // 返回最大深度
}

                        当然,求左子树、右子树的最大深度,与单独求根节点的最大深度还是有区别,如果我们确信直径一定经过根节点,那么将root.left、root.right传递过去,分别得到左右子树的深度,然后相加即可,伪代码为:

// 伪代码
public int solution(TreeNode root){
    int lD=maxDepth(root.left); // maxDepth即前一段代码定义的求最大深度函数
    int rD=maxDepth(node.right); //

    return lD+rD;
}

        至此,解题难点1分析的化简题已经完成,需要重新考虑问题。

        我们知道,直径不一定经过根节点。

解题难点2分析:

        我们有手段得到二叉树的遍历,即使用递归或迭代的方式,如果我们借助TreeNode数组,保存所有节点。

        然后,计算每个节点的直径【也就是,对于每个节点,计算经过该节点的直径】

        最后,进行判断,得到最大直径,就可以解决问题。

        得到遍历序列,解决直径问题:

                已经有根节点作为示例,在此不再讲解,代码展示如下:

class Solution {
    public int diameterOfBinaryTree(TreeNode root) {
        List<TreeNode> list=new ArrayList<TreeNode>();
        int res=0;

        getData(list, root); // 得到中序遍历序列(哪个序都一样)

        // 遍历节点
        for(TreeNode data:list){
            int lD=maxDepth(data.left);
            int rD=maxDepth(data.right);
            res=Math.max(res, lD+rD); // 比较节点的直径和目前最大直径
        }
        return res;
    }

    public void getData(List<TreeNode> list, TreeNode root){
        if(root==null){
            return;
        }

        getData(list, root.left);
        list.add(root);
        getData(list, root.right);
    }

    private int maxDepth(TreeNode node){
        if(node==null){
            return 0;
        }
        int lD=maxDepth(node.left);
        int rD=maxDepth(node.right);
    
        return Math.max(lD, rD)+1;
    }
}

        目前,已经真正解决了该问题,不过时间复杂度比较高,首先是中序遍历,大概在O(n),然后是迭代,迭代中嵌套计算直径的函数,大概在O(n^2),因此总体大约达到O(n^2);

解题难点3分析:

        优化:这道题目,使用这种算法,会有很多数据被重复计算,比如下图(示例图):

        在计算节点1的直径时,已经计算到3的节点不可能是直径(因为它是叶子节点),还有4、5也不可能是直径,最多计算1、2,就可以返回答案。

        优化:如何避免大量重复运算?

                既然已经知道经由某个节点的直径,我们是否可以从该方法入手改造?

                我们知道,maxDepth函数的作用,是通过比较左右子树的深度,得到最大深度。

                同时,我们又需要左右子树的深度,来辅助计算直径【同样看成通过某个节点的直径】

                那么,可以直接改造吧?

                对于节点root,得到其左右深度lD、rD,使lD+rD,比较lD+rD与原直径,哪个大哪个就是新直径。【这道题的子问题,其实是求深度,因此原直径不能在函数体中定义,而是定义全局变量】

代码:

class Solution {
    int ans;
    public int diameterOfBinaryTree(TreeNode root) {
        ans = 0;
        depth(root);
        return ans;
    }
    public int depth(TreeNode node) {
        if (node == null) {
            return 0; // 访问到空节点了,返回0
        }
        int L = depth(node.left); // 左儿子为根的子树的深度
        int R = depth(node.right); // 右儿子为根的子树的深度
        ans = Math.max(ans, L+R); // 更新直径。
        return Math.max(L, R) + 1; // 返回该节点为根的子树深度
    }
}

以上内容即我想分享的关于力扣热题16

的一些知识。

        我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。

  • 31
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值