算法通关村第六关—二叉树的层次遍历经典问题(白银)

news2024/11/21 0:13:50

         二叉树的层次遍历经典问题

一、层次遍历简介

广度优先遍历又称层次遍历,过程如下:截屏2023-12-01 13.05.51.png
 层次遍历就是从根节点开始,先访问根节点下面一层全部元素,再访问之后的层次,图里就是从左到右一层一层的去遍历二叉树,先访问3,之后访问的左右子孩子9和20,之后分别访问9和20的左右子孩子[8,13]和[15,17],最后得到结果[3,9,20,8,13,15,17]。
 这里的问题是怎么将遍历过的元素的子孩子保存一下呢,例如访问9时其左右子孩子8和13应该先存一下,直到处理20之后才会处理。使用队列来存储能完美解决上述问题,例如上面的图中:
截屏2023-12-01 13.08.13.png
 思考:该过程不复杂,如果能将树的每层次分开了,是否可以整点新花样?首先,能否将每层的元素顺序给反转一下呢?能否奇数行不变,只将偶数行反转呢?能否将输出层次从低到root逐层输出呢?再来,既然能拿到每一层的元素了,能否找到当前层最大的元素?最小的元素?最右的元素(右视图)?最左的元素(左视图)?整个层的平均值?
 很明显都可以!这么折腾有啥用呢?没啥用!但这就是层次遍历的高频算法题!这就是LeetCode里经典的层次遍历题!
102.二叉树的层序遍历
107.二叉树的层次遍历II
199.二叉树的右视图
637.二叉树的层平均值
429.N叉树的前序遍历
515.在每个树行中找最大值
116.填充每个节点的下一个右侧节点指针
117.填充每个节点的下一个右侧节点指针l
103锯齿层序遍历
除此之外,在深度优先的题目里,有些仍然会考虑层次遍历的实现方法。

二、基本的层序遍历与变换

2.1 层序遍历基础版

最简单的情况:仅仅层序遍历并输出所有元素
利用前面讲到的借助队列的方法来实现

List<Integer>simpleLevelorder(TreeNode root){
	if(root == null){
		return new ArrayList<Integer>();
	}
	List<Integer>res = new ArrayList<Integer>();
	LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
	//将根节点放入队列中,然后不断遍历队列
	queue.add(root);
	//有多少元素执行多少次
	while (queue.size()>0){
		//获取当前队列的长度,这个长度相当于当前这一层的节点个数
		TreeNode t = queue.remove();
		res.add(t.val);
		if(t.left != null){
			queue.add(t.left);
		}
		if(t.right = null){
			queue.add(t.right);
		}
	}
	return res;
}

2.2 二叉树的层序遍历(模版:非常重要)

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c23f7ece838341c491b3918718f405e5.pn
LeetCode102:给你一个二叉树,请你返回其按层序遍历得到的节点值。(即逐层地,从左到右访问所有节点)。
注意:与前面相比,要区分每一层
截屏2023-12-01 13.24.31.png
如何判断某一层遍历完了?可以设定一个size变量来标记。size表示某一层的元素个数,只要元素出队,size-1.当size==0时,该层已经遍历完了。(代码里的做法与这里殊途同归)
然后此时队里的元素个数为下一层元素的个数,因此让size标记为下一层的元素个数即可处理下一层了。
最后,把每一层遍历到的结点放入一个结果集中,将其返回即可

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root == null) return new ArrayList<List<Integer>>();
		//创建集合和队列
        List<List<Integer>> list = new ArrayList<>();
        LinkedList<TreeNode> queue = new LinkedList();
		//将根结点放入队列,方便进入循环
        queue.add(root);
        while(queue.size() > 0){
			//everylist集合用来记录某一层的所有元素
             List<Integer> everylist = new ArrayList<>();
			//size记录某一层的元素个数
            int size = queue.size();
			//从队头遍历某一层的所有元素,并把下一层的元素存放到队尾
            for(int i = 0; i < size; i++){
                 TreeNode t = queue.remove();
                 everylist.add(t.val);
                if(t.left != null)queue.add(t.left);
                if(t.right != null)queue.add(t.right);
            }
			//此时i == size,该层所有元素都遍历完了
            list.add(everylist);
        }
        return list;
    }
}

2.3 层序遍历-自底往上

LeetCode107.给定一个二叉树,返回其节点值自底向上的层序遍历。(即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)。例如给定的二叉树与结果为下图:
截屏2023-12-01 15.06.52.png 截屏2023-12-01 15.07.00.png
实现的代码几乎与正常的层序遍历相同,只在list.add(0,everylist)这里把后面遍历的层次放到list的前面,实现自底往上的效果。

为了降低在结果列表的头部添加一层节点值的列表的时间复杂度,结果列表可以使用链表的结构,即创建list时把ArrayList改成LinkedList。在链表头部添加一层节点值的列表的时间复杂度是O(1)。

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        if(root == null) return new LinkedList<List<Integer>>();
        List<List<Integer>> list = new LinkedList<>();
        LinkedList<TreeNode> queue = new LinkedList();
        queue.add(root);
        while(queue.size() > 0){
             List<Integer> everylist = new ArrayList<>();
            int size = queue.size();
            for(int i = 0; i < size; i++){
                 TreeNode t = queue.remove();
                 everylist.add(t.val);
                if(t.left != null)queue.add(t.left);
                if(t.right != null)queue.add(t.right);
            }
            list.add(0,everylist);
        }
        return list;
    }
}

2.4 二叉树的锯齿形层次遍历

LeetCode103题,要求是:给定一个二叉树,返回其节点值的锯齿形层序遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
例如:给定二叉树[3,9,20,nul,null,15,7]
截屏2023-12-01 15.16.43.png 截屏2023-12-01 15.16.50.png
依旧是对2.2对代码进行改写。因为这里有时是从左到右,有时是从右到左,自然可以想到把代码里的队列改成双端队列,然后再设置过参数判断元素存放位置。
如果从左至右,我们每次将被遍历到的当前层元素插入至双端队列的末尾。如果从右至左,我们每次将被遍历到的元素插入至双端队列的头部。

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        if(root == null) return new ArrayList<List<Integer>>();
        List<List<Integer>> list = new LinkedList<>();
        Queue<TreeNode> queue = new LinkedList();
        queue.add(root);
        Boolean judgeleft = true;
        while(queue.size() > 0){
            //创建成双端队列
             Deque<Integer> everylist = new LinkedList<>();
            int size = queue.size();
			
            for(int i = 0; i < size; i++){
                TreeNode t = queue.remove();
                //从左到右放队尾
                if(judgeleft) everylist.offerLast(t.val);
                //从右到左放队首
                else everylist.offerFirst(t.val);
                if(t.left != null)queue.add(t.left);
                if(t.right != null)queue.add(t.right);
            }
            judgeleft = !judgeleft;
            //把双端队列转类型
            list.add(new LinkedList<Integer>(everylist));
        }
        return list;
    }
}

2.5 N叉树的层序遍历

LeetCode.429给定一个N叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)树的序列化输入是用层序遍历,每组子节点都由null值分隔(参见示例)
截屏2023-12-01 17.55.43.png
N叉树的定义如下,就是一个值,加一个列表,其类型仍然是Node:截屏2023-12-01 17.55.52.png
这个也是102的扩展,很简单的广度优先,与二叉树的层序遍历基本一样,借助队列即可实现。只有在保存当前结点的孩子时,不是左右孩子,有N个,可以使用增强for循环来遍历

class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        if(root == null)return new ArrayList<List<Integer>>();
        List<List<Integer>> list = new ArrayList();
        Queue<Node> queue = new LinkedList();
        queue.add(root);
        while(queue.size() > 0){
            List<Integer> everylist = new ArrayList();
            int size = queue.size();
            for(int i = 0; i < size; i++){
                Node t = queue.remove();
                everylist.add(t.val);
				//与2.2唯一不同之处,利用增强for循环遍历所有孩子
                for(Node node1: t.children) {
                    if(node1 != null) queue.add(node1);
                }
            }
            list.add(everylist);
        }
        return list;
    }
}

三、几个处理每层元素的题目

如果我们拿到了每一层的元素,那是不是可以利用一下造几个题呢?例如每层找最大值、平均值、最右侧的值呢?当然可以。LeetCode.里就有三道非常明显的题目。
515.在每个树行中找最大值
637.二叉树的层平均值
199.二叉树的右视图

3.1 在每个树行中找最大值

LeetCode515题目要求:给定一棵二叉树的根节点root,请找出该二叉树中每一层的最大值。
截屏2023-12-01 18.04.21.png
与模版相比,在while循环中不需要创建另外一个集合,只需要用max记录最大值,然后添加进list集合即可

class Solution {
    public List<Integer> largestValues(TreeNode root) {
        if(root == null) return new ArrayList<Integer>();
        List<Integer> maxlist = new ArrayList();
        Queue<TreeNode> queue = new LinkedList();
        queue.add(root);
        while(queue.size() > 0){
            int max = Integer.MIN_VALUE;
            int size = queue.size();
            for(int i = 0; i < size; i++){
                TreeNode t =queue.remove();
                max = Math.max(max,t.val);
                if(t.left != null) queue.add(t.left);
                if(t.right != null) queue.add(t.right);
            }
            maxlist.add(max);
        }
        return maxlist;
    }
}

3.2 在每个树行中找平均值

LeetCode637要求给定一个非空二叉树,返回一个由每层节点平均值组成的数组
这些题目其实都差不多一个思路,只要中间一些处理修改一下就行。这道题就把每层元素的和记录下来,除以元素个数就行

class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
        List<Double> list = new ArrayList();
        if(root == null) return list;
        Queue<TreeNode> queue = new LinkedList();
        queue.add(root);
        while(queue.size() > 0){
            int size = queue.size();
            double sum = 0;
            for(int i = 0; i < size; i++){
                TreeNode t = queue.remove();
                sum += t.val;
                if(t.left != null) queue.add(t.left);
                if(t.right != null) queue.add(t.right);
            }
            list.add(sum/size);
        }
        return list;
    }
}

3.3 二叉树的右视图

LeetCode199题目要求是:给定一个二叉树的根节点root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。例如:
截屏2023-12-01 22.22.05.png
这道题只要把每层最后一个元素保存起来即可

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null) return list;
        Queue<TreeNode> queue = new LinkedList();
        queue.add(root);
        while(queue.size() > 0){
            int size = queue.size();
            for(int i = 0; i < size; i++){
                TreeNode t = queue.remove();
                if(size - 1 == i) list.add(t.val);
                if(t.left != null) queue.add(t.left);
                if(t.right != null) queue.add(t.right);
            }
        }
        return list;
    }
}

3.4 最底层最左边

上面这个层次遍历的思想可以方便的解决LeetCode513.二叉树最底层最左边的值的问题:给定一个二叉树的根节点root,请找出该二叉树的最底层最左边节点的值。假设二叉树至少有一个节点。
截屏2023-12-01 22.26.25.png截屏2023-12-01 22.26.32.png
如果是正常遍历,是从左到右,那最后会遍历到最底层最右边,与题目正好相反。所以,我们在遍历存储下一层元素的时候,可以先保存右边的节点,再保存左边的节点。这样,遍历就是从右到左,题目就解出来啦。

class Solution {
    public int findBottomLeftValue(TreeNode root) {
		//可以记录值;也可记录节点,最后返回值
        int num = 0;
        Queue<TreeNode> queue = new LinkedList();
        queue.add(root);
        while(queue.size() > 0){
            int size = queue.size();
            for(int i = 0; i < size; i++){
                TreeNode t = queue.remove();
                num = t.val;
                if(t.right != null) queue.add(t.right);
                if(t.left != null) queue.add(t.left);
            }
        }
        return num;
    }
}

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

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

相关文章

基于mps的pytorch 多实例并行推理

背景 大模型训练好后&#xff0c;进行部署时&#xff0c;发现可使用的显卡容量远大于模型占用空间 。是否可以同时加载多个模型实例到显存空间&#xff0c;且能实现多个实例同时并发执行&#xff1f;本次实验测试基于mps的方案&#xff0c;当请求依次过来时&#xff0c;多个相…

NDK打印android日志

首先在cpp文件中 引入 #include <android/log.h> 然后就可以使用 __android_log_print方法&#xff0c;第一个参数是log level&#xff0c;第二个是tag&#xff0c;第三个是日志内容。 #include <jni.h> #include <string> #include <android/log.h&g…

2023-12-01 事业-代号s-如何装修“高转化“首页

摘要: 2023-12-01 事业-代号s-如何装修"高转化"首页 如何装修"高转化"首页 影响独立站转化率6大因素:产品、素材、受众、落地页、结算流程、复购。 今天就来分享下,独立站高转化首页如何装修?整个网站首页框架应该放置什么内容? 传统设计 VS 8P设计 …

模糊C均值(Fuzzy C-means,FCM)聚类的python程序代码的逐行解释,看完你也会写!!

文章目录 前言一、本文的原始代码二、代码的逐行详细解释总结 前言 接上一篇博客&#xff0c;详细解释FCM聚类的程序代码&#xff01;&#xff01; 一、本文的原始代码 import numpy as np import matplotlib.pyplot as plt from sklearn import datasets import skfuzzy as…

【开源】基于JAVA的厦门旅游电子商务预订系统

项目编号&#xff1a; S 030 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S030&#xff0c;文末获取源码。} 项目编号&#xff1a;S030&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 景点类型模块2.2 景点档案模块2.3 酒…

多线程(初阶五:wait和notify)

目录 一、概念 二、用法 &#xff08;1&#xff09;举个栗子&#xff1a; &#xff08;2&#xff09;wait和notify的使用 1、没有上锁的wait 2、当一个线程被wait&#xff0c;但没有其他线程notify来释放这个wait 3、两个线程&#xff0c;有一个线程wait&#xff0c;有一…

React项目使用NProgress作为加载进度条

React项目使用NProgress作为加载进度条 0、效果1、react安装依赖2、使用3.进度条颜色设置 文档参考&#xff1a;https://zhuanlan.zhihu.com/p/616245086?utm_id0 0、效果 如下&#xff0c;可全局在页面顶部有一条进度条 1、react安装依赖 yarn add nprogress通过以上安装…

vue之mixin混入

vue之mixin混入 mixin是什么&#xff1f; 官方的解释&#xff1a; 混入 (mixin) 提供了一种非常灵活的方式&#xff0c;来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时&#xff0c;所有混入对象的选项将被“混合”进入该组件本身的…

二次元检测设备导轨修复指南

二次元检测设备是一种高精度的测量仪器&#xff0c;用于检测物体表面的形状、尺寸和精度等。直线导轨是二次元检测设备中最重要的组成部分之一&#xff0c;它的精度和稳定性直接影响到设备的测量结果和可靠性&#xff0c;因此&#xff0c;对导轨进行修复和保养是非常重要的。 直…

网络类型解析(基础):探索通信世界的多样面貌

在当今数字化时代&#xff0c;网络已经成为人们生活和工作中不可或缺的一部分。从个人设备之间的直接通信到全球范围的数据传输&#xff0c;不同类型的网络为我们提供了多种连接方式和通信选择。透过对这些网络类型的解析&#xff0c;我们将更好地理解它们的特点、优势和适用场…

九章量子计算机:探索量子世界的革命性工具

九章量子计算机:探索量子世界的革命性工具 一、引言 九章量子计算机的推出,是近年来科技界最为引人瞩目的成就之一。这款基于量子力学的计算机,以其独特的计算方式和潜在的应用前景,引发了全球范围内的关注和讨论。本文将深入探讨九章量子计算机的原理、技术特点、应用前景…

nodejs_vue+vscode美容理发店会员管理系统un1dm

按照设计开发一个系统的常用流程来描述系统&#xff0c;可以把系统分成分析阶段&#xff0c;设计阶段&#xff0c;实现阶段&#xff0c;测试阶段。所以在编写系统的说明文档时&#xff0c;根据系统所处的阶段来描述系统的内容。 绪论&#xff1a;这是对选题的背景&#xff0c;意…

PHP实践:用ThinkPHP6完整实现用户分页功能

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师…

WPF Mvvm模式下面如何将事件映射到ViewModel层

前言 平常用惯了Command绑定,都快忘记传统的基于事件编程模式了,但是Commond模式里面有个明显的问题,就是你无法获取到事件源的参数。很多大聪明肯定会说,这还不简单,通过自己写控件,给控件加个自定义属性不就行了,想要啥事件就写啥事件进去,完全自主可控。但是对于写…

Day04:每日一题:2661. 找出叠涂元素

2661. 找出叠涂元素 给你一个下标从 0 开始的整数数组 arr 和一个 m x n 的整数 矩阵 mat 。 arr 和 mat 都包含范围 [1&#xff0c;m * n] 内的 所有 整数。从下标 0 开始遍历 arr 中的每个下标 i &#xff0c;并将包含整数 arr[i] 的 mat 单元格涂色。请你找出 arr 中在 mat…

【数电笔记】逻辑代数的基本定律、常用公式

说明&#xff1a; 笔记配套视频来源&#xff1a;B站 逻辑代数的基本定律 1. 常量间的运算 2. 逻辑变量与常量的运算 3. 与普通代数相似的定律 4. 摩根定律&#xff08;反演律&#xff09; 5. 等式证明方法例题 逻辑代数的常用公式 1. 吸收律 2. 冗余律 3. 示例应用 4. 关于异…

stm32的中断复习

(https://img-blog.csdnimg.cn/4fa918bc1ebb41118410bc9a41d4b296.jpg)

lxml 总结

xm 和 lxml库 哪个更好用点 1. 性能&#xff1a; lxml 通常比 xml.etree.ElementTree 更快。lxml 使用了 C 编写的底层解析器&#xff0c;因此在处理大型 XML 文档时可能更高效。 如果性能对你的应用很重要&#xff0c;特别是在处理大型 XML 文件时&#xff0c;选择 lxml 可能…

shell编程系列(9)-使用cut选择列

文章目录 前言使用cut选择列选择特定的列 结语 前言 前面的文章介绍了sed命令&#xff0c;sed可以帮我们处理文本列&#xff0c;这边文章介绍cut命令&#xff0c;cut命令可以帮我们选择想要的列&#xff0c;在文本处理时候结合sed命令&#xff0c;就可以精准定位了。 cut命令是…

前端面试高频考点—TCP vs UDP

目录 简介&#xff1a; 区别&#xff1a; 应用选择&#xff1a; tcp为什么需要三次握手&#xff1f; 简介&#xff1a; TCP(传输控制协议)和UDP&#xff08;用户数据报协议&#xff09; TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议&#xff0c;是专门为了在不…