二叉树题目:填充每个结点的下一个右侧结点指针 II

news2024/11/17 17:51:22

文章目录

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

题目

标题和出处

标题:填充每个结点的下一个右侧结点指针 II

出处:117. 填充每个结点的下一个右侧结点指针 II

难度

4 级

题目描述

要求

给定一个二叉树,定义如下:

struct Node {
    int val;
    Node *left;
    Node *right;
    Node *next;
}

填充它的每个 next \texttt{next} next 指针,让这个指针指向其下一个右侧结点。如果找不到下一个右侧结点,则将 next \texttt{next} next 指针设置为 NULL \texttt{NULL} NULL

初始状态下,所有 next \texttt{next} next 指针都被设置为 NULL \texttt{NULL} NULL

示例

示例 1:

示例 1

输入: root   =   [1,2,3,4,5,null,7] \texttt{root = [1,2,3,4,5,null,7]} root = [1,2,3,4,5,null,7]
输出: [1,#,2,3,#,4,5,7,#] \texttt{[1,\#,2,3,\#,4,5,7,\#]} [1,#,2,3,#,4,5,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next \texttt{next} next 指针,以指向其下一个右侧结点,如图 B 所示。序列化的输出按层序遍历排列,同一层结点由 next \texttt{next} next 指针连接, ‘#’ \texttt{`\#'} ‘#’ 标志着每一层的结束。

示例 2:

输入: root   =   [] \texttt{root = []} root = []
输出: [] \texttt{[]} []

数据范围

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

进阶

  • 你只能使用常量级额外空间。
  • 使用递归解题也符合要求,本题中递归程序占用的栈空间不计入额外的空间。

解法一

思路和算法

由于每个结点的下一个右侧结点一定和该结点位于同一层,因此只要在同一层的结点之间填充下一个右侧结点指针即可。可以使用层序遍历实现。

层序遍历的方法为从根结点开始依次遍历每一层的结点,同一层的结点的遍历顺序为从左到右。在层序遍历的过程中需要区分不同结点所在的层,确保每一轮访问的结点为同一层的全部结点。

使用队列存储待访问的结点,初始时将根结点入队列。每一轮访问结点之前首先得到队列内的元素个数,然后访问这些结点,并将这些结点的非空子结点入队列。该做法可以确保每一轮访问的结点为同一层的全部结点。

每次访问结点时,将待访问的结点出队列。如果当前访问的结点不是当前层的最后一个结点,则此时的队首元素即为当前结点的下一个右侧结点(注意当前结点已经出队列),将当前结点的下一个右侧结点指针指向队首元素。

遍历结束之后返回根结点,此时二叉树中每个结点的下一个右侧结点指针都被填充。

代码

class Solution {
    public Node connect(Node root) {
        if (root == null) {
            return root;
        }
        Queue<Node> queue = new ArrayDeque<Node>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                Node node = queue.poll();
                if (i < size - 1) {
                    node.next = queue.peek();
                }
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
        }
        return root;
    }
}

复杂度分析

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

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

解法二

思路和算法

也可以使用深度优先搜索填充每个结点的下一个右侧结点指针。具体做法是使用前序遍历,依次访问二叉树的根结点、左子树和右子树,由于前序遍历满足同一层结点被访问的顺序为从左到右,因此可以在前序遍历过程中填充每个结点的下一个右侧结点指针。由于每个结点的下一个右侧结点一定和该结点位于同一层,因此需要使用哈希表存储每一层已经访问过的的最右侧结点。

前序遍历过程中,对于每个结点,执行如下操作。

  1. 如果当前结点所在层已经有被访问过的结点,则从哈希表中得到该层已经访问过的最右侧结点,并将已经访问过的最右侧结点的下一个右侧结点指针指向当前结点。如果当前结点所在层没有被访问过的结点,则跳过这一步。

  2. 将哈希表中当前结点所在层已经访问过的最右侧结点设为当前结点。

遍历结束之后返回根结点,此时二叉树中每个结点的下一个右侧结点指针都被填充。

代码

class Solution {
    public Node connect(Node root) {
        Map<Integer, Node> rightmost = new HashMap<Integer, Node>();
        Deque<Node> nodeStack = new ArrayDeque<Node>();
        Deque<Integer> depthStack = new ArrayDeque<Integer>();
        Node node = root;
        int depth = 0;
        while (!nodeStack.isEmpty() || node != null) {
            while (node != null) {
                if (rightmost.containsKey(depth)) {
                    rightmost.get(depth).next = node;
                }
                rightmost.put(depth, node);
                nodeStack.push(node);
                depthStack.push(depth);
                node = node.left;
                depth++;
            }
            node = nodeStack.pop().right;
            depth = depthStack.pop() + 1;
        }
        return root;
    }
}

复杂度分析

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

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

解法三

思路和算法

为了将空间复杂度降到 O ( 1 ) O(1) O(1),需要使用其他方法遍历二叉树并填充每个结点的下一个右侧结点指针。

如果根结点为空或者根结点为叶结点,则不需要填充任何结点的下一个右侧结点指针,直接返回原始二叉树。以下只考虑根结点不为空且不为叶结点的情况。

规定根结点所在层是第 0 0 0 层,从根结点开始填充每一层的下一个右侧结点指针。由于根结点所在层没有其他结点,因此不需要填充根结点的下一个右侧结点指针。

对于 k ≥ 0 k \ge 0 k0,当第 k k k 层的全部结点的下一个右侧结点指针填充完毕时,第 k k k 层的全部结点组成一个单向链表,每个结点的下一个右侧结点指针指向单向链表中的后一个结点。

当第 k k k 层有结点时,第 k k k 层的全部结点的子结点即为第 k + 1 k + 1 k+1 层的全部结点。从左到右依次遍历第 k k k 层的每个结点,对于每个结点依次得到其左子结点和右子结点,则遍历子结点的顺序为从左到右遍历第 k + 1 k + 1 k+1 层的所有结点的顺序,遍历子结点的过程中即可填充第 k + 1 k + 1 k+1 层的所有结点的下一个右侧结点指针。

填充下一个右侧结点指针需要维护两项信息,一是同一层结点的最左侧结点,二是上一个被访问的结点。这两项信息都可以在遍历过程中完成,具体做法如下。

  1. 将第 k + 1 k + 1 k+1 层的第一个被访问的结点设为第 k + 1 k + 1 k+1 层结点的最左侧结点。

  2. 如果上一个被访问的子结点不为空,则将上一个被访问的子结点的下一个右侧结点指针指向当前子结点。

  3. 将当前子结点赋给上一个被访问的子结点。

当第 k k k 层遍历完毕且第 k + 1 k + 1 k+1 层的下一个右侧结点指针填充完毕时,移动到第 k + 1 k + 1 k+1 层的最左侧结点,继续填充其余层的下一个右侧结点指针。当遍历到的层没有结点时,结束操作,此时所有结点的下一个右侧结点指针填充完毕。

代码

class Solution {
    Node nextStart;
    Node prev;

    public Node connect(Node root) {
        if (root == null) {
            return root;
        }
        Node start = root;
        while (start != null) {
            Node node = start;
            nextStart = null;
            prev = null;
            while (node != null) {
                update(node.left);
                update(node.right);
                node = node.next;
            }
            start = nextStart;
        }
        return root;
    }

    public void update(Node curr) {
        if (curr == null) {
            return;
        }
        if (nextStart == null) {
            nextStart = curr;
        }
        if (prev != null) {
            prev.next = curr;
        }
        prev = curr;
    }
}

复杂度分析

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

  • 空间复杂度: O ( 1 ) O(1) O(1)

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

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

相关文章

“系统的UI”——SystemUI

SystemUI的实现 以StatusBar为例&#xff0c;来分析下Android系统具体是如何实现它们的。 相关代码分为两部分&#xff0c;即&#xff1a; Service部分 代码路径&#xff1a;frameworks/base/services/java/com/android/server。 应用部分 代码路径&#xff1a;frameworks…

基于云计算的区域LIS系统系统源码

在医疗机构内部&#xff0c;院内实验室主要负责本院临床科室的检验&#xff0c;院内LIS系统必须满足实验室日常的标本处理入库、仪器联机、检验结果处理、报告打印、报告发布、检验信息统计、检验信息报告发布、标本流程、外部医疗机构检验报告调阅等工作。 在医疗机构间&#…

【JUC系列-04】精通Synchronized底层的实现原理

JUC系列整体栏目 内容链接地址【一】深入理解JMM内存模型的底层实现原理https://zhenghuisheng.blog.csdn.net/article/details/132400429【二】深入理解CAS底层原理和基本使用https://blog.csdn.net/zhenghuishengq/article/details/132478786【三】熟练掌握Atomic原子系列基本…

嘉泰实业举行“互联网金融知识社区”“安全理财风险讲座”等活动

每一次暖心的沟通都是一次公益,真诚不会因为它的渺小而被忽略;每一声问候都是一次公益,善意不会因为它的普通而被埋没。熟悉嘉泰实业的人都知道,这家企业不但擅长在金融理财领域里面呼风唤雨,同时也非常擅长在公益事业当中践行,属于企业的责任心,为更多有困难的群体带来大爱的传…

【数据结构】搜索树MapSet

目录 1.搜索树 1.1概念 1.2查找 1.3插入 1.4删除 2.Map 2.1map说明 2.2TreeMap和HashMap 2.3常用方法 3.Set 3.1set说明 3.2TreeSet和HashSet 3.3常用方法 1.搜索树 1.1概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者具有以下性质&…

Linux CentOS7命令及命令行

Linux CentOS7中命令及命令行是非常重要的概念。对大多数初学者来说是既熟悉又了解甚少。本文初步讨论这方面的内容&#xff0c;与同行者交流。 一、命令 命令又称为指令&#xff0c;&#xff08;英语命令 command&#xff0c;可用简写cmd表示&#xff09;&#xff0c;在终端…

爬虫逆向实战(30)-某查查股东关联公司(HmacSHA512)

一、数据接口分析 主页地址&#xff1a;某查查 1、抓包 通过抓包可以发现数据接口是api/people/getRelatCompany 2、判断是否有加密参数 请求参数是否加密&#xff1f; 无 请求头是否加密&#xff1f; 通过查看“标头”可以发现&#xff0c;请求头中有一个key和value都是…

怎么把视频转换成mp4格式

怎么把视频转换成mp4格式&#xff1f;如今&#xff0c;随着科技的不断发展&#xff0c;我们在工作中接触到的多媒体视频格式也越来越多。其中&#xff0c;MP4作为一种广泛兼容的视频格式&#xff0c;在许多软件中都能轻松播放&#xff0c;并且成为了剪辑与裁剪视频时大家常用的…

视频画质修复神器,视频修复专家告诉你怎样提高画质

如果您的视频画质模糊、失真或者过于昏暗&#xff0c;那么本文将给大家分享一个非常实用的视频修复技巧。利用一些工具增强视频细节和清晰度的高级技术&#xff0c;向您呈现最详细的视频修复教程。 修复视频画质的话也可以使用去噪滤镜和锐化滤镜。 调整视频分辨率:将视频的分…

【图文并茂】c++介绍之队列

1.1队列的定义 队列&#xff08;queue&#xff09;简称队&#xff0c;它也是一种操作受限的线性表&#xff0c;其限制为仅允许在表的一端进行插入操作&#xff0c;而在表的另一端进行删除操作 一些基础概念&#xff1a; 队尾&#xff08;rear&#xff09; &#xff1a;进行插…

C++-map和set

本期我们来学习map和set 目录 关联式容器 键值对 pair 树形结构的关联式容器 set multiset map multimap 关联式容器 我们已经接触过 STL 中的部分容器&#xff0c;比如&#xff1a; vector 、 list 、 deque 、forward_list(C11)等&#xff0c;这些容器统称为序列式…

为何电商界都重视社交媒体客户服务软件

如今&#xff0c;几乎每个企业都有自己的像Facebook、WhatsApp和telegram等社交媒体渠道&#xff0c;客户可以利用这些渠道找到产品相关内容&#xff0c;发现有关产品或服务的其他信息&#xff0c;并接收有用的优惠和特别优惠。然而&#xff0c;真正成功的电子商务公司仅仅拥有…

element-plus 表格-自定义样式实现

效果如下 代码如下 <template><h2>表格自定义样式</h2><div style"background-color: cadetblue; height: 600px;"><div class"regulaContainer"><el-table ref"tableRef" :data"tableData" border …

Spring 自定义注解 面向切面编程

Spring 自定义注解 JDK元注解规范 Documented -注解是否将包含在JavaDoc中 Retention -什么时候使用该注解(生命周期)RetentionPolicy.SOURCE: 在变异阶段丢弃&#xff0c;这些注解在编译结束之后就不再有任何意义&#xff0c;所以不会写入到字节码中RetentionPolicy.CLASS:…

2023年数学建模国赛A 定日镜场的优化设计思路分析

构建以新能源为主体的新型电力系统&#xff0c;是我国实现“碳达峰”“碳中和”目标的一项重要措施。塔式太阳能光热发电是一种低碳环保的新型清洁能源技术[1]。定日镜是塔式太阳能光热发电站&#xff08;以下简称塔式电站&#xff09;收集太阳能的基本组件&#xff0c;其底座由…

【算法训练-字符串 三】最长公共子串、最长公共子序列

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【】&#xff0c;使用【】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&#xff1a;目标公…

Promise异步请求/async-await

问题&#xff1a;调接口时&#xff0c;非以往的函数异步请求去调接口。而是用到了Promise中.then方法 Promise Promise是一种用于处理异步操作的对象。它代表了一个尚未完成但预计会未来完成的操作&#xff0c;并提供了一种结构化的方式来处理操作的结果。它起到代理作用&…

合宙Air724UG LuatOS-Air LVGL API控件-键盘 (Keyboard)

键盘 (Keyboard) LVGL 可以添加触摸键盘&#xff0c;但是很明显&#xff0c;使用触摸键盘的话必须要使用触摸的输入方式&#xff0c;否则无法驱动键盘。 示例代码 function keyCb(obj, e)-- 默认处理事件lvgl.keyboard_def_event_cb(keyBoard, e)if(e lvgl.EVENT_CANCEL)the…

后流量时代的跨境风口:Facebook广告

Facebook拥有超过25亿各个年龄段和人群的每月活跃用户&#xff0c;可以帮助您接触世界各地的相关消费者。无论您是需要吸引新的潜在客户还是吸引回头客访问您的在线商店&#xff0c;Facebook广告都可以为电子商务提供丰厚的投资回报&#xff1b;无论您是在沃尔玛、eBay、亚马逊…

Spring-MVC的crud增删改查--详细讲解

目录 一.前言 二.crud---配置文件 2.1 pom.xml文件 2.2 web.xml文件 2.3 spring-context.xml 2.4 spring-mvc.xml 2.5 spring-MyBatis.xml 2.6 jdbc.properties数据库配置文件 2.7 generatorConfig.xml 2.8 日志文件log4j 三.后台 3.1 pageBean.java 3.2 pageTag 3.…