遍历思路与子问题思路:详解二叉树的基本操作(二)

简介: 这篇内容介绍了二叉树的基本操作,包括通过遍历和子问题思路来解决不同的问题。

遍历思路与子问题思路:详解二叉树的基本操作 (一)+  https://developer.aliyun.com/article/1520508?spm=a2c6h.13148508.setting.14.61564f0e51sYB8



2、获取叶子节点的个数


遍历思路


用成员变量leafCount标记叶子节点的数目。当左子树与右子树都为null时,这个结点是叶子结点,此时令leafCount++即可。


    //获取叶子节点的个数
    int leafCount;
    public void getLeafNodeCount3(TreeNode root) {
        if(root == null) {
            return;
        }
        if(root.left == null && root.right == null) {
            leafCount++;
        }
        getLeafNodeCount(root.left);
        getLeafNodeCount(root.right);
    }


子问题思路

整棵二叉树的叶子结点个数 = 左子树的叶子结点个数+右子树的叶子结点个数

叶子结点 = 左子树和右子树都为null的结点

掌握这两个结论,可以写出如下代码:


    //获取叶子节点的个数
    public int getLeafNodeCount2(TreeNode root) {
        if(root == null) {
            return 0;
        }
 
        if(root.left == null && root.right == null) {
            return 1;
        }
 
        int left = getLeafNodeCount2(root.left);    //左子树中叶子节点的个数
        int right = getLeafNodeCount2(root.right);    //右子树中叶子节点的个数
 
        return left+right;
    }


    //获取叶子节点的个数
    public int getLeafNodeCount(TreeNode root) {
        if(root == null) {
            return 0;
        }
 
        int left = getLeafNodeCount(root.left);
        int right = getLeafNodeCount(root.right);
 
        if(left == 0 && right == 0) {
            return 1;
        }
        return left+right;
    }


从结果上看。两段代码都是正确的。


注意:在一些问题上,子问题的思路分析能够使问题变得更清晰、简便。


3、获取第K层结点的个数


子问题思路


该题的思路比较巧妙。要求第K层的结点个数,一整棵树的第K层 = 它的左子树的第K-1层 = 它的右子树的第K-1层。

一棵树第K层的结点个数 = 左子树第K-1层的结点个数+右子树第K-1层的结点个数。



可以写出如下代码,如果对代码不理解,可以参照前序遍历的图示将递归画图展开。


1.    //获取第k层结点的个数
    public int getKLevelNodeCount(TreeNode root,int k) {
        if(root == null) {
            return 0;
        }
        if(k == 1) {
            return 1;
        }
 
        int left = getKLevelNodeCount(root.left,k-1);
        int right = getKLevelNodeCount(root.right,k-1);
 
        return left+right;
    }


4、求树的高度

🔗LeetCode-104. 二叉树的最大深度

子问题思路

根据二叉树高度的定义:树的高度或深度是树中结点的最大层次。

一棵二叉树的高度 = 左子树高度与右子树高度的最大值 + 1

由此通项公式,可以写出求二叉树高度的代码:


    //二叉树的高度
    public int getHeight(TreeNode root) {
        if(root == null) {    //空树高度为0 返回0
            return 0;
        }
 
        int left = getHeight(root.left);    //记录左子树的高度
        int right = getHeight(root.right);    //记录右子树的高度
        return Math.max(left,right) + 1;    //返回左子树和右子树的高度的最大值+1,即树的高度
    }


特别注意:若不用变量left和right保存左右子树的高度递归结果,而是直接将递归语句代入求最大值的语句,是不合适的:




虽然在本地IDE中运行,结果不会有错,但在在线OJ中运行,会超出时间限制。因为没有用额外的变量来保存递归结果的话,最终return语句中实际上有四个递归语句,有大量的递归的重复计算,这是非常耗时的。


复杂度分析

时间复杂度:O(N),每一个结点都要求一次以它为根的树的高度,也就是每一个结点实际都会访问的。


空间复杂度:O(height),和树的高度有关系。若是单分支的树,则高度为N,空间复杂度即为O(N),若是满二叉树,则高度为logN,空间复杂度即为O(logN。


5、二叉树的中序遍历

与前面前序遍历的代码相似,只是改变了对元素的操作与递归左右子树的顺序(左根右)。


遍历思路


    //中序遍历 递归 遍历思路
    public void inOrder(TreeNode root) {
        if(root == null) {
            return;
        }
        inOrder(root.left);
        System.out.print(root.value + " ");
        inOrder(root.right);
    }


子问题思路

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
        public List<Integer> inorderTraversal(TreeNode root) {
 
        List<Integer> list = new ArrayList<>();
 
        if (root == null) {
            return list;
        }
 
        List<Integer> left = inorderTraversal(root.left);
        list.addAll(left);
 
        list.add(root.val);
 
        List<Integer> right = inorderTraversal(root.right);
        list.addAll(right);
 
        return list;
    }
}


6、二叉树的后序遍历

与前面前序遍历的代码相似,只是改变了对元素的操作与递归左右子树的顺序(左右根)。


遍历思路


    //后序遍历 递归 遍历思路
    public void postOrder(TreeNode root) {
        if(root == null) {
            return;
        }
        postOrder(root.left);
        postOrder(root.right);
        System.out.print(root.value + " ");
    }


子问题思路


/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
        public List<Integer> postorderTraversal(TreeNode root) {
 
        List<Integer> list = new ArrayList<>();
 
        if (root == null) {
            return list;
        }
 
        List<Integer> left = postorderTraversal(root.left);
        list.addAll(left);
 
        List<Integer> right = postorderTraversal(root.right);
        list.addAll(right);
 
        list.add(root.val);
 
        return list;
    }
}


7、检测值为value的元素是否存在,若存在返回该元素的结点

子问题思路

检测值为value的元素在整棵二叉树是否存在 = 检测左树是否存在该节点(如果存在便结束检测并返回) + 检测右树是否存在该结点(如果存在便结束检测并返回)


该代码的思路类似于前序遍历的思路。每遍历到一个结点,就判断这个结点的值是否与给定的value相匹配。



    //检测值为value的元素是否存在  子问题思路
    public TreeNode find(TreeNode root, int val) {
        if(root == null) {
            return null;
        }
        if(root.value == val) {
            return root;
        }
 
        TreeNode left = find(root.left,val);
        if(left != null) {
            return left;
        }
        TreeNode right = find(root.right,val);
 
        return right;
    }
相关文章
|
12月前
|
数据可视化 API 数据库
低代码是什么?2025低代码技术详解:平台分类、用户群体与发展趋势分析
低代码(Low Code)是一种通过可视化工具和预构建组件,以少量或无代码快速开发应用的技术。2014年Forrester定义其为“用最少手工编码快速开发并部署应用的技术”,随后Gartner推广aPaaS/iPaaS概念推动其发展。
|
机器学习/深度学习 计算机视觉 iOS开发
RT-DETR改进策略【模型轻量化】| 替换骨干网络 CVPR-2024 RepViT 轻量级的Vision Transformers架构
RT-DETR改进策略【模型轻量化】| 替换骨干网络 CVPR-2024 RepViT 轻量级的Vision Transformers架构
955 0
RT-DETR改进策略【模型轻量化】| 替换骨干网络 CVPR-2024 RepViT 轻量级的Vision Transformers架构
|
5G 调度 芯片
5G 帧结构 |带你读《5G空口特性与关键技术》之七
虽然在较高的载波频率下通常不使用较小的子载波间隔,但是参数集可以独立于频段进行选择。不同子载波间隔可用于不同的场景下。如对于室外宏覆盖和微小区,可以采用 30kHz 子载波间隔;而室内站则可以采用 60kHz 子载波间隔;对于毫米波,则可以采用更大的子载波间隔,如 120kHz。
13692 3
5G 帧结构 |带你读《5G空口特性与关键技术》之七
|
SQL 关系型数据库 MySQL
分布式事物【 认识事物、脏写、脏读、不可重复读、幻读】(一)-全面详解(学习总结---从入门到深化)
分布式事物【 认识事物、脏写、脏读、不可重复读、幻读】(一)-全面详解(学习总结---从入门到深化)
429 1
分布式事物【 认识事物、脏写、脏读、不可重复读、幻读】(一)-全面详解(学习总结---从入门到深化)
|
存储 Java 分布式数据库
【分布式计算框架】HBase数据库编程实践
【分布式计算框架】HBase数据库编程实践
487 1
|
存储 编解码 缓存
系统设计面试的行家指南(中)(3)
系统设计面试的行家指南(中)(3)
309 0
|
JavaScript 前端开发 安全
深入探究iframe:网页嵌入的魔法盒子(上)
深入探究iframe:网页嵌入的魔法盒子(上)
|
druid 关系型数据库 MySQL
高并发下 MySQL Statement Cancellation Timer 的线程数暴涨
高并发下 MySQL Statement Cancellation Timer 的线程数暴涨
|
存储 SQL 弹性计算
阿里DDD四弹拜读
阿里殷浩大牛写了DDD系统文章,现在已经更新到每四篇,有很多异于常规的地方,收获良多,总结一下
2035 0
阿里DDD四弹拜读
|
存储 机器学习/深度学习 缓存
链路追踪(Tracing)其实很简单——分布式链路追踪的挑战与限制
作者:夏明(涯海) 创作日期:2022-07-14 专栏地址:【稳定大于一切】【稳定大于一切】作为一门新兴技术,分布式链路追踪的技术演进史并不算长,仅有十数年。目前,它仍处于不断被探索、快速迭代的周期。为了更好的了解与应用分布式链路追踪技术,我们来看下它目前面临的几项关键挑战与限制。关键挑战与应对分...
587 0
链路追踪(Tracing)其实很简单——分布式链路追踪的挑战与限制