222.完全二叉树的节点个数 |递归优化思路 + 复杂度分析

news2024/11/18 2:48:05

完全二叉树的节点个数

leetcode : https://leetcode.cn/problems/count-complete-tree-nodes/

递归思路

递归的思路很简单, 假设们要统计一棵树的节点数, 那么 只要统计根节点的左子树的节点数, 和右子树的节点数加上根节点即可

image-20230112200228510

那么, 假设我们要统计左子树的节点数, 其实就变成了统计以2为根节点的树的节点个数

image-20230112200507426

进一步分解, 假设我们要统计上面树的节点数, 就变成了统计左子树 4 和右子树 5 的两棵子树的节点数的子问题, 这个问题逐渐分解, 分解到最小单位

image-20230112200723197

统计上面这样一棵树的节点个数, 这棵树同样是需要统计左子树的节点数 + 右子树的节点数 + 1(root)

思考 : 假设一个大的计算整棵树的大问题, 可以像现在这样一步一步的分解成一个个子问题的情况下, 我们就可以使用递归

递归流程

下面的流程是计算普通二叉树的流程 :

1.确定函数参数以及返回值

  • 这个函数的目的是为了计算一棵树的总的节点数, 所以参数为 count(TreeNode root)
  • 返回值为 int , 也就是树的节点数
  • private int count(TreeNode root)

2.终止条件

我们在设置终止条件时, 一定要将问题分解到最小时, 去考虑终止条件, 一定不能在大问题上去考虑, 就比如这道题,

image-20230112201404213
  1. 左右节点都为 NULL , 直接返回 1 , 因为只有root节点存在

3.当前层逻辑

当前层也就是左右节点至少有一个存在, 需要统计左右, 或者某一个

  1. 左节点存在, 右节点为NULL , 此时就要去统计 左子树的节点数 + 1 (root) : 1 + count(root.left)
  2. 左节点为NULL, 右节点存在 (本题是二叉树, 不存在这样的情况, 但是我们暂时也把这种情况放进去, 适用于所有的树) : 1 + count(root.right)
  3. 左节点都存在, 则需要统计左子树 和 右子树节点数

实际上本题是完全二叉树, 满二叉树的计算公式是假设层数是h, 节点数是 2 h − 1 2^h - 1 2h1 , 只需要让指针分别从左右两边遍历计算深度, 判断是否一致,

如果是满二叉树, 直接使用公式计算, 如果不是, 按照普通二叉树计算即可

递归代码

1.代码实现

/**
 * 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 int countNodes(TreeNode root) {
        
        if(root == null) {
            return 0;
        }

        return count(root);
    }


    private int count(TreeNode root) {

        // 处理全空的情况
        if(root.left == null && root.right == null) {
            return 1;
        }

        // 处理只有一个节点为空
        if(root.left == null && root.right != null) {
            return 1 + count(root.right);
        }

        if(root.left != null && root.right == null) {
            return 1 + count(root.left);
        }

        // 左右都不为空
        int leftCount = count(root.left);
        int rightCount = count(root.right);

        // 左右子树的节点数量 + 本身的
        return leftCount + rightCount + 1;

    }
}

上面的代码是为了便于理解, 如果想要简化的话, 很简单 :

class Solution {
    public int countNodes(TreeNode root) {
         if (root == null) return 0;
    return 1 + countNodes(root.left) + countNodes(root.right);
    }
}

2.优化版

优化版是考虑了满二叉树, 先判断从左边遍历和从右边遍历的层高是否相同, 如果相同则可以使用 公式直接计算, 不同再使用普通方法计算

/**
 * 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 int countNodes(TreeNode root) {
        
        TreeNode le = root, ri = root;
        // 判断是否是完全二叉树
        int hl = 0, hr = 0;
        
        while(le != null) {
            le = le.left;
            hl++;
        }

        while(ri != null) {
            ri = ri.right;
            hr++;
        }

        if(hl == hr) {
            return (int)Math.pow(2, hl) -1;
        }

        return 1 + countNodes(root.left) + countNodes(root.right);
    }
}

3.时间复杂度分析

计算普通树递归的时间复杂度是 O ( n ) O(n) O(n) , 因为其实是访问了所有的节点, 计算满二叉树的时间复杂度是层高 O ( l o g N ) O(log_{N}) O(logN) , N N N 是节点的个数, h h h 是层高
N = 2 h − 1 = = > h = l o g N + 1 N = 2^h - 1 ==> h = log_{N+1} \\ N=2h1==>h=logN+1
但是实际上的时间复杂度是 O ( l o g N ∗ l o g N ) O(log_{N} * log_{N}) O(logNlogN) , 因为最坏情况一定有一半是满二叉树. 另一半是完全二叉树

  • 第一层, root

  • 第二层, 有两棵树, 右边是满二叉树 : l o g N logN logN, 左边不确定

image-20230112215012864

  • 第三层, 右边满二叉树 : l o g N logN logN , 左边不确定
image-20230112214416355
  • 第四层, 右边满二叉树 : l o g N logN logN , 左边不确定
image-20230112214453525
  • 第五层, 也就是最后一层, 都是完全二叉树
image-20230112214703431

层高是 O ( l o g N ) O(logN) O(logN) , 每一层需要计算的时间复杂度是 O ( l o g N ) O(logN) O(logN) , 所以实际上的时间复杂度是 O ( l o g N ∗ l o g N ) O(logN * logN) O(logNlogN)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160188.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Centos7 Minimal 版本基本配置记录

每次搭测试环境之前都需要先装一台干净的虚拟机,然而 Centos7 Minimal 版本快速装完之后还需要配置:网络、国内源、一些基础工具(net-tools、vim)等才能远程连接和使用。记录一下,方便下次快速配置使用。 目录 1、网…

Docker镜像加载原理

文章目录什么是镜像 ?Docker镜像加载原理UnionFS (联合文件系统)Docker镜像加载原理分层理解镜像Commit什么是镜像 ? 镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件&#xff0…

C语言-自定义类型-枚举和联合(11.3)

目录 思维导图: 1.枚举 1.1 枚举类型的定义 1.2 枚举的优点 1.3 枚举的使用 2. 联合(共用体) 2.1 联合类型的定义 2.2 联合的特点 2.3 联合大小的计算 写在最后: 思维导图: 1.枚举 1.1 枚举类型的定义 例&…

Spring复习(三)

AOP AOP(Aspect Oriented Programming)面向切面编程,aop是一种设计思想,是oop面向对象编程的一种补充和完善,它通过预编译方式和运行期间动态代理的方式达成在不修改源代码的情况下,实现对业务逻辑的增强。 相关术语 横切关注点…

论文投稿指南——中文核心期刊推荐(农业基础科学)

【前言】 🚀 想发论文怎么办?手把手教你论文如何投稿!那么,首先要搞懂投稿目标——论文期刊 🎄 在期刊论文的分布中,存在一种普遍现象:即对于某一特定的学科或专业来说,少数期刊所含…

【计组】存储器层次结构全景和局部性原理--《深入浅出计算机组成原理》(八)

课程链接:深入浅出计算机组成原理_组成原理_计算机基础-极客时间 目录 一、存储器层次结构全景 (一)SRAM (二)DRAM (三)存储器的层级结构 二、局部性原理 一、存储器层次结构全景 &…

Eureka读时加写锁,写时加读锁,到底是故意为之还是一个bug?

在对于读写锁的认识当中,我们都认为读时加读锁,写时加写锁来保证读写和写写互斥,从而达到读写安全的目的。但是就在我翻Eureka源码的时候,发现Eureka在使用读写锁时竟然是在读时加写锁,写时加读锁,这波操作…

uni-app | 从零创建一个新项目以及关于网络请求配置和分包

一、uni-app简介uni-app 是一个使用 Vue.js 开发所有前端应用的框架。开发者编写一套代码,可发布到 iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台。二、开发工具uni-app官方推荐使用HBuilderX来…

【JS 逆向百例】X-Bogus 逆向分析,JSVMP 纯算法还原

声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未…

72小时灵感冲刺,创意就该这么玩 | LigaAI Hackathon特别策划

2023 年 1 月 9 日至 12 日,LigaAI 团队全员出逃:放下迭代,暂缓需求,处处充斥着「可以摸鱼➕卷死他们」的矛盾又欢乐的气息。职场和远程的伙伴们无一不在热烈讨论、积极组队、抢占会议室、搜刮零食饮料…… 是什么让矜持内敛的技…

微信小程序的启动和渲染过程(加组件分类和组件的基本使用以及API分类)

小程序的启动过程 把小程序的代码包下载到本地解析app.json全局配置文件执行app.js小程序入口文件,调用App()创建小程序实例渲染小程序首页小程序启动完成 小程序页面渲染的过程 加载解析页面的.json配置文件加载页面的.wxml模板和.wxss样式执行页面的.js文件,调用page(创建…

网站建设的具体流程

有网站制作需求的朋友想了解一下网站建设的具体流程,防止不良的网站制作公司的业务人员用虚假的工作量欺骗自己报出高价,米贸搜详细给你整理一下一个网站的建设有哪些流程:1.在线或当面沟通网站制作需求,详细沟通网站的功能需求、设计风格以及…

功率放大模块的作用是什么(功率放大器模块的应用范围)

功率放大模块是一种能够把开关电源、数字功放集成到一起的放大模块。很多人对于功率放大模块的作用是什么以及功率放大模的应用范围都不清楚,下面就来详细的为大家介绍下什么是功率放大模块以及功率放大模块的基础知识。 一、什么是功率放大模块 功率放大模块是一种…

谷歌AR应用挑战赛上那些富有创意的AR项目集锦

前不久,谷歌面向全球100多个国家和地区的开发者们推出ARCore Geospatial API挑战赛,获奖的AR应用可得到1000美元到1.2万美元的奖金,支持多种不同的内容类别,比如AR导航、AR游戏、AR娱乐等等。据悉,Geospatial API是谷歌…

贵阳某小区一次HC小区管理系统自研道闸故障解决记录

一次HC小区管理系统自研道闸故障解决记录,方便其他小伙伴出现问题的时候提供解决思路 早上九点钟 客户说道闸用不了,这个客户不是物业,而是科技公司,他们给物业安装的道闸。 问题描述是,mqtt 也重启了,物…

Allegro如何打开锁定走线线宽的功能操作指导

Allegro如何打开锁定走线线宽的功能操作指导 在做PCB设计的时候,有时需要用到锁定线宽的功能,让走线一直保持固定的线宽不变化,例如下图 任何网络的走线在任何地方都是固定20mil的线宽 具体操作如下 选择菜单的Setup选择User Preferences

解决IE页面打不开及白屏问题

IE浏览器控制台报错如下: 解决办法: ① public文件下新建TextEncoder.js /* eslint-disable */ ; var textEncoder (function(window) {if (undefined ! window.TextEncoder) { return }function _TextEncoder() {// --DO NOTHING}_TextEncoder.prot…

steam搬砖项目详细介绍

一、项目介绍 其实,Steam就是一个全球的游戏平台,搬砖主要是搬的一款火遍全球的游戏CSGO的装备和饰品。CS听说过吧,这款游戏就是CS的一个系列。(通俗易懂的理解就是,从国外steam游戏平台购买装备,再挂到国内…

分销商城小程序开发

武汉微驱动科技有限公司 目前电商行业正在蓬勃发展,越来越多的商家想要利用各种社交关系,用社交软件扩大经营规模,小程序分销是最近很火爆的流行趋势,不过有不少商家还不清楚“什么是小程序分销模式?小程序分销商城是什…

gerapy部署项目报错:ModuleNotFoundError: No module named ...

使用gerapy部署我的项目,rebuild的时候是成功的,但是deploy的时候失败了,报错: Client 1 Failed to Deploy 没有显示具体的错误,只能到gerapy的部署目录找日志。 根据 ll 命令,找到了最新的日志文件&…