当前位置: 首页 > news >正文

二叉树题目:二叉树最大宽度

文章目录

  • 题目
    • 标题和出处
    • 难度
    • 题目描述
      • 要求
      • 示例
      • 数据范围
  • 前言
  • 解法一
    • 思路和算法
    • 代码
    • 复杂度分析
  • 解法二
    • 思路和算法
    • 代码
    • 复杂度分析

题目

标题和出处

标题:二叉树最大宽度

出处:662. 二叉树最大宽度

难度

5 级

题目描述

要求

给定一个二叉树的根结点 root \texttt{root} root,返回给定的树的最大宽度

树的最大宽度是所有层中的最大宽度

每一层的宽度定义为两端结点(最左和最右的非空结点)之间的长度,两端结点之间的空结点也计入长度的计算。

题目保证答案在 32 \texttt{32} 32 为有符号整数范围内。

示例

示例 1:

示例 1

输入: root = [1,3,2,5,3,null,9] \texttt{root = [1,3,2,5,3,null,9]} root = [1,3,2,5,3,null,9]
输出: 4 \texttt{4} 4
解释:最大宽度出现在第 3 \texttt{3} 3 层,宽度为 4 \texttt{4} 4 5,3,null,9 \texttt{5,3,null,9} 5,3,null,9)。

示例 2:

示例 2

输入: root = [1,3,null,5,3] \texttt{root = [1,3,null,5,3]} root = [1,3,null,5,3]
输出: 2 \texttt{2} 2
解释:最大宽度出现在第 3 \texttt{3} 3 层,宽度为 2 \texttt{2} 2 5,3 \texttt{5,3} 5,3)。

示例 3:

示例 3

输入: [1,3,2,5] \texttt{[1,3,2,5]} [1,3,2,5]
输出: 2 \texttt{2} 2
解释:最大宽度出现在树的第 2 \texttt{2} 2 层,宽度为 2 \texttt{2} 2 3,2 \texttt{3,2} 3,2)。

数据范围

  • 树中结点数目在范围 [1, 3000] \texttt{[1, 3000]} [1, 3000]
  • -100 ≤ Node.val ≤ 100 \texttt{-100} \le \texttt{Node.val} \le \texttt{100} -100Node.val100

前言

为了得到二叉树的最大宽度,需要得到二叉树每一层中的每个结点的位置。每一层的宽度取决于该层最左和最右的非空结点的位置,除了根结点以外的每个结点的位置由该结点的父结点和该结点是左子结点还是右子结点决定。

考虑满二叉树每一层中的每个结点的位置。定义根结点位于第 0 0 0 层,则满二叉树的第 k k k 层有 2 k 2^k 2k 个结点,从左到右的每个结点的位置依次是 0 0 0 2 k − 1 2^k - 1 2k1,满二叉树的第 k + 1 k + 1 k+1 层有 2 k + 1 2^{k + 1} 2k+1 个结点,从左到右的每个结点的位置依次是 0 0 0 2 k + 1 − 1 2^{k + 1} - 1 2k+11。由于满二叉树的第 k + 1 k + 1 k+1 层的结点数目是第 k k k 层的结点数目的 2 2 2 倍,因此对于第 k k k 层中位置是 pos \textit{pos} pos 的结点,其左子结点和右子结点在第 k + 1 k + 1 k+1 层中的位置分别是 pos × 2 \textit{pos} \times 2 pos×2 pos × 2 + 1 \textit{pos} \times 2 + 1 pos×2+1

上述关于结点位置的定义同样适用于不是满二叉树的情况。对于每个非空结点,只要知道该结点在当前层中的位置,即可知道该结点的每个子结点在下一层中的位置,因此可以知道每个结点在对应层中的位置。

解法一

思路和算法

由于需要计算二叉树每一层的宽度,然后得到最大宽度,因此可以使用层序遍历实现。

从根结点开始依次遍历每一层的结点,在层序遍历的过程中需要区分不同结点所在的层,确保每一轮访问的结点为同一层的全部结点。遍历每一层结点之前首先得到当前层的结点数,即可确保每一轮访问的结点为同一层的全部结点。对于每一层,记录该层的最左和最右非空结点的位置,然后计算该层的宽度。

为了记录每个结点的位置,需要使用两个队列,分别存储结点和结点的位置,这两个队列分别为结点队列和位置队列,初始时将根结点和位置 0 0 0 分别入两个队列。在遍历每一层之前,位置队列的队首元素即为该层的最左非空结点的位置,遍历该层的每个结点时,将每个结点的非空子结点和子结点对应的位置分别入两个队列,该层的最后一个结点即为该层的最右非空结点,计算两端结点之间的长度,得到该层的宽度,并用该层的宽度更新最大宽度。

遍历结束之后,即可得到二叉树的最大宽度。

代码

class Solution {public int widthOfBinaryTree(TreeNode root) {int maxWidth = 0;Queue<TreeNode> nodeQueue = new ArrayDeque<TreeNode>();Queue<Integer> posQueue = new ArrayDeque<Integer>();nodeQueue.offer(root);posQueue.offer(0);while (!nodeQueue.isEmpty()) {int leftmost = posQueue.peek();int size = nodeQueue.size();for (int i = 0; i < size; i++) {TreeNode node = nodeQueue.poll();int pos = posQueue.poll();if (i == size - 1) {int width = pos - leftmost + 1;maxWidth = Math.max(maxWidth, width);}TreeNode left = node.left, right = node.right;if (left != null) {nodeQueue.offer(left);posQueue.offer(pos * 2);}if (right != null) {nodeQueue.offer(right);posQueue.offer(pos * 2 + 1);}}}return maxWidth;}
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的结点数。每个结点都被访问一次。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的结点数。空间复杂度主要是队列空间,队列内元素个数不超过 n n n

解法二

思路和算法

也可以使用深度优先搜索计算二叉树最大宽度。具体做法是使用前序遍历,依次访问二叉树的根结点、左子树和右子树,由于前序遍历满足同一层结点被访问的顺序为从左到右,因此同一层结点中第一个被访问的结点一定是该层最左的非空结点。使用哈希表存储每一层的最左的非空结点的位置(以下简称「最左位置」),当访问到某个结点时,如果哈希表中没有该结点所在层的最左位置,则在哈希表中将当前层对应的最左位置设为该结点的位置。

对于访问到的每个非空结点,可以得到当前层内该结点的位置和最左位置,计算当前层内该结点和最左的非空结点之间的宽度,并更新二叉树的最大宽度。遍历结束之后,即可得到二叉树的最大宽度。

代码

class Solution {Map<Integer, Integer> leftmostMap = new HashMap<Integer, Integer>();int maxWidth = 0;public int widthOfBinaryTree(TreeNode root) {dfs(root, 0, 0);return maxWidth;}public void dfs(TreeNode node, int depth, int pos) {leftmostMap.putIfAbsent(depth, pos);maxWidth = Math.max(maxWidth, pos - leftmostMap.get(depth) + 1);TreeNode left = node.left, right = node.right;if (left != null) {dfs(left, depth + 1, pos * 2);}if (right != null) {dfs(right, depth + 1, pos * 2 + 1);}}
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的结点数。每个结点都被访问一次。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的结点数。空间复杂度主要是哈希表和栈空间,取决于二叉树的高度,最坏情况下二叉树的高度是 O ( n ) O(n) O(n)

相关文章:

  • 136. 只出现一次的数字 --力扣 --JAVA
  • Kubernetes介绍和环境部署
  • k8s 对外服务之 Ingress( LB + ingress)
  • cadence virtuoso 修改电路原理图背景颜色
  • Ansible的变量(vars,register,set_fact)
  • pandas读写json的知识点
  • docker/ nvidia-docker
  • Postman小白安装和注册入门教程
  • HCIP---OSPF思维导图
  • 内存管理
  • 基于ubuntu22.04手动安装openstack——2023.2版本(最新版)的问题汇总
  • Redis 和 Mysql 如何保证数据一致性
  • 假冒 Skype 应用程序网络钓鱼分析
  • C语言--每日五道选择题--Day6
  • WPF ToggleButton 主题切换动画按钮
  • CSS进阶篇--用CSS开启硬件加速来提高网站性能
  • ESLint简单操作
  • FastReport在线报表设计器工作原理
  • javascript面向对象之创建对象
  • JavaScript中的对象个人分享
  • Java知识点总结(JavaIO-打印流)
  • python3 使用 asyncio 代替线程
  • 经典排序算法及其 Java 实现
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • 一个完整Java Web项目背后的密码
  • scrapy中间件源码分析及常用中间件大全
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • ​queue --- 一个同步的队列类​
  • ​你们这样子,耽误我的工作进度怎么办?
  • # 透过事物看本质的能力怎么培养?
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • #pragma once
  • #调用传感器数据_Flink使用函数之监控传感器温度上升提醒
  • ( 10 )MySQL中的外键
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (Oracle)SQL优化技巧(一):分页查询
  • (vue)页面文件上传获取:action地址
  • (非本人原创)我们工作到底是为了什么?​——HP大中华区总裁孙振耀退休感言(r4笔记第60天)...
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (全注解开发)学习Spring-MVC的第三天
  • (转)大型网站架构演变和知识体系
  • (转)关于多人操作数据的处理策略
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • (轉)JSON.stringify 语法实例讲解
  • .net core 6 集成 elasticsearch 并 使用分词器
  • .NET Core 版本不支持的问题
  • .Net IOC框架入门之一 Unity
  • .NET国产化改造探索(一)、VMware安装银河麒麟
  • /dev/sda2 is mounted; will not make a filesystem here!
  • @NestedConfigurationProperty 注解用法
  • [2016.7 Day.4] T1 游戏 [正解:二分图 偏解:奇葩贪心+模拟?(不知如何称呼不过居然比std还快)]
  • [20190113]四校联考
  • [AutoSar]BSW_OS 02 Autosar OS_STACK
  • [BZOJ] 1001: [BeiJing2006]狼抓兔子