二叉树题目:二叉树展开为链表

news2024/9/28 11:15:47

文章目录

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

题目

标题和出处

标题:二叉树展开为链表

出处:114. 二叉树展开为链表

难度

3 级

题目描述

要求

给你二叉树的根结点 root \texttt{root} root,请你将它展开为一个单链表:

  • 展开后的单链表应该同样使用 TreeNode \texttt{TreeNode} TreeNode 类,其中右子结点指针指向链表中的下一个结点,左子结点指针总是 null \texttt{null} null
  • 展开后的单链表应该与二叉树前序遍历顺序相同。

示例

示例 1:

示例 1

输入: root   =   [1,2,5,3,4,null,6] \texttt{root = [1,2,5,3,4,null,6]} root = [1,2,5,3,4,null,6]
输出: [1,null,2,null,3,null,4,null,5,null,6] \texttt{[1,null,2,null,3,null,4,null,5,null,6]} [1,null,2,null,3,null,4,null,5,null,6]

示例 2:

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

示例 3:

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

数据范围

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

进阶

你可以使用 O(1) \texttt{O(1)} O(1) 额外空间展开这棵树吗?

解法一

思路和算法

这道题要求按照二叉树前序遍历的顺序将二叉树展开为单链表。最直观的解法是对二叉树前序遍历并记录前序遍历的结点顺序,然后更改结点之间的指针指向。对于每个结点,将其左子结点指针设为 null \text{null} null,将其右子结点指针设为前序遍历顺序的后一个结点,其中前序遍历顺序的最后一个结点的右子结点指针也设为 null \text{null} null

代码

下面的代码为递归实现二叉树前序遍历的做法。

class Solution {
    List<TreeNode> traversal = new ArrayList<TreeNode>();

    public void flatten(TreeNode root) {
        preorder(root);
        int size = traversal.size();
        for (int i = 0; i < size; i++) {
            TreeNode node = traversal.get(i);
            node.left = null;
            node.right = i == size - 1 ? null : traversal.get(i + 1);
        }
    }

    public void preorder(TreeNode node) {
        if (node == null) {
            return;
        }
        traversal.add(node);
        preorder(node.left);
        preorder(node.right);
    }
}

下面的代码为迭代实现二叉树前序遍历的做法。

class Solution {
    public void flatten(TreeNode root) {
        List<TreeNode> traversal = new ArrayList<TreeNode>();
        Deque<TreeNode> stack = new ArrayDeque<TreeNode>();
        TreeNode node = root;
        while (!stack.isEmpty() || node != null) {
            while (node != null) {
                traversal.add(node);
                stack.push(node);
                node = node.left;
            }
            node = stack.pop().right;
        }
        int size = traversal.size();
        for (int i = 0; i < size; i++) {
            node = traversal.get(i);
            node.left = null;
            node.right = i == size - 1 ? null : traversal.get(i + 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)

解法二

思路和算法

解法一需要在前序遍历之后将二叉树展开为链表,因为展开为链表的过程会改变结点之间的指针关系,破坏二叉树的结构,导致丢失子结点的信息。

之所以会丢失子结点的信息,是因为在对左子树遍历时,没有存储右子结点的信息,在遍历完左子树之后才获得右子结点的信息。为了不丢失子结点的信息,可以修改前序遍历的迭代实现,在遍历左子树之前就获得右子结点的信息并存入栈内,此时可以在前序遍历的同时将二叉树展开为链表。

修改后的前序遍历的做法是,每次将当前访问的结点出栈,如果该结点的子结点不为空,则依次将右子结点和左子结点入栈。注意入栈顺序不可颠倒,因为左子结点先被访问因此需要先出栈。

展开为单链表的做法是,维护上一个访问的结点 prev \textit{prev} prev 和当前访问的结点 curr \textit{curr} curr,初始时 prev = null \textit{prev} = \text{null} prev=null。对于每个结点,当 prev ≠ null \textit{prev} \ne \text{null} prev=null 时,将 prev \textit{prev} prev 的左子结点指针设为 null \text{null} null,将 prev \textit{prev} prev 的右子结点指针设为 curr \textit{curr} curr。访问当前结点 curr \textit{curr} curr 之后,将 prev \textit{prev} prev 的值设为 curr \textit{curr} curr,继续访问下一个结点,直到遍历结束。

代码

class Solution {
    public void flatten(TreeNode root) {
        if (root == null) {
            return;
        }
        Deque<TreeNode> stack = new ArrayDeque<TreeNode>();
        stack.push(root);
        TreeNode prev = null;
        while (!stack.isEmpty()) {
            TreeNode curr = stack.pop();
            if (prev != null) {
                prev.left = null;
                prev.right = curr;
            }
            if (curr.right != null) {
                stack.push(curr.right);
            }
            if (curr.left != null) {
                stack.push(curr.left);
            }
            prev = curr;
        }
    }
}

复杂度分析

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

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

解法三

思路和算法

使用常数空间实现二叉树展开为链表,则不能使用栈或者其他数据结构存储结点,只能改变结点之间的指针关系。

在遍历二叉树的过程中改变结点之间的指针关系,将二叉树展开为链表。对于每个结点,考虑子结点的情况。

  • 如果没有子结点,则该结点是叶结点,遍历结束。

  • 如果只有左子结点,则将左子树变成右子树,然后移动到右子结点继续展开剩下的结点。

  • 如果只有右子结点,则移动到右子结点继续展开剩下的结点。

  • 如果左子结点和右子结点都存在,则根据前序遍历顺序,遍历完左子树之后访问右子结点,因此需要找到左子树中最后被访问的结点,即当前结点的前驱结点,前驱结点为当前结点的左子树中的最右边的结点。找到当前结点的前驱结点之后,操作如下。

    1. 将当前结点的右子树移动到前驱结点的右子树的位置。

    2. 将当前结点的左子树移动到当前结点的右子树的位置。

    3. 将当前结点的左子结点设为空。

左子结点和右子结点都存在的情况的展开操作也适用于只有左子结点的情况,因此对于每个结点的展开操作如下。

  1. 如果当前结点的左子结点不为空,则执行上述展开操作。如果当前结点的左子结点为空,则不执行展开操作。

  2. 移动到当前结点的右子结点。

重复上述操作,直到当前结点变为空,此时二叉树展开为链表的操作结束。

考虑如下二叉树,共有 7 7 7 个结点,其前序遍历顺序是 [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] [1,2,3,4,5,6,7] [1,2,3,4,5,6,7]

图 1

从根结点 1 1 1 开始遍历。结点 1 1 1 的左子结点不为空,因此找到结点 1 1 1 的前驱结点 4 4 4,将以结点 5 5 5 为根结点的右子树移动到结点 4 4 4 的右子树的位置,然后将结点 1 1 1 的左子树(以结点 2 2 2 为根结点)移动到右子树的位置。

图 2

结点 2 2 2 的左子结点不为空,因此找到结点 2 2 2 的前驱结点 3 3 3,将以结点 4 4 4 为根结点的右子树移动到结点 3 3 3 的右子树的位置,然后将结点 2 2 2 的左子树(以结点 3 3 3 为根结点)移动到右子树的位置。

在这里插入图片描述

结点 3 3 3 和结点 4 4 4 的左子结点都为空,因此不执行展开操作。

结点 5 5 5 的左子结点不为空,因此找到结点 5 5 5 的前驱结点 6 6 6,将以结点 7 7 7 为根结点的右子树移动到结点 6 6 6 的右子树的位置,然后将结点 5 5 5 的左子树(以结点 6 6 6 为根结点)移动到右子树的位置。

图 4
结点 6 6 6 和结点 7 7 7 的左子结点都为空,因此不执行展开操作。

遍历结束,此时得到二叉树展开后的链表。

代码

class Solution {
    public void flatten(TreeNode root) {
        TreeNode node = root;
        while (node != null) {
            if (node.left != null) {
                TreeNode predecessor = node.left;
                while (predecessor.right != null) {
                    predecessor = predecessor.right;
                }
                predecessor.right = node.right;
                node.right = node.left;
                node.left = null;
            }
            node = node.right;
        }
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的结点数。遍历和展开过程中,每个结点都被访问一次,寻找前驱结点的过程中,每个结点最多被访问一次,因此每个结点最多被访问两次。

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

后记

读者也许已经发现,解法三和莫里斯遍历非常相似。和莫里斯遍历相比,这道题不需要将前驱结点的右指针指向当前结点,而是将当前结点的右子树移动到前驱结点的右子树的位置,因此解法三可以看成莫里斯遍历的简化版。

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

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

相关文章

8 从0开始学PyTorch | PyTorch中自动计算梯度、使用优化器

上一节&#xff0c;我们写了很多代码&#xff0c;但是不知道你有没有注意&#xff0c;那些代码看起来跟PyTorch关系并不是很大啊&#xff0c;貌似很多都是Python原生代码&#xff1f; 如果你是这样的感觉&#xff0c;那我要告诉你&#xff0c;你感觉的没有错。前面主要在于机制…

下面告诉你音频转换工具有哪些

今天我想和大家聊一聊音频转换工具。你是不是有时候想把一首酷炫的歌曲转换成你喜欢的音频格式&#xff0c;或者想把录音文件转成可编辑的格式&#xff1f;别担心&#xff0c;这里有一些超赞的音频转换工具&#xff0c;可以帮你解决这些问题&#xff01;无论是从MP3到WAV&#…

武汉大学计算机考研分析

关注我们的微信公众号 姚哥计算机考研 更多详情欢迎咨询 武汉大学&#xff08;A-&#xff09;考研难度&#xff08;☆☆☆☆☆&#xff09; 武汉大学计算机考研招生学院是计算机学院、国家网络安全学院和测绘遥感信息工程国家重点实验室。目前均已出拟录取名单。 武汉大学计…

Redis的3大特殊数据类型(1)-BitMap

BitMap(位图/位数组)是Redis2.2.0版本中引入的一种新数据类型&#xff0c;该数据类型本质是一个仅含0和1的二进制字符串。因此可以把 Bitmap 想象成一个以位为单位的数组&#xff0c;数组的每个单元只能存储 0 和 1&#xff0c;数组的下标在 Bitmap 中叫做偏移量 offset&#x…

volatile关键字和ThreadLocal

作用&#xff1a; 1.线程的可见性&#xff1a;当一个线程修改一个共享变量时&#xff0c;另外一个线程能读到这个修改的值。 2. 顺序一致性&#xff1a;禁止指令重排序。 线程之间的共享变量存储在主内存中&#xff08;Main Memory&#xff09;中&#xff0c;每个线程都一个都…

StarRocks Friends 上海站活动回顾(含 PPT 下载链接)

6月17日&#xff0c; StarRocks & Friends 上海站活动如期而至&#xff0c;近百位社区小伙伴参与交流活动&#xff1b;针对 StarRocks 存算分离、StarRocks 在业界的应用实践、以及 StarRocks 与 BI 结合、湖仓一体规划等话题展开激烈的交流互动。 本文总结了技术交流活动…

未来的彩电,彩电的未来

疫情后的首个线上大促已经结束&#xff0c;“史上投入最大618”也没能抵住彩电市场整体的需求疲软。 根据奥维云网线上推总数据&#xff0c;2023年618期间&#xff0c;中国彩电线上市场零售量规模为249.9万台&#xff0c;同比下降12.9%&#xff1b;零售额规模为79.7亿元&#…

配电柜(箱)使用防雷浪涌保护器的作用和方案

配电箱是电力系统中的重要组成部分&#xff0c;负责将电力从供电系统输送到各个电器设备。然而&#xff0c;由于天气状况和其他因素的影响&#xff0c;电力系统可能会受到雷击引起的浪涌电压的威胁。为了保护配电箱和其中的设备免受浪涌电压的破坏&#xff0c;我们需要在配电箱…

Redis中3大特殊数据结构(2)-HyperLogLog

HyperLogLog算法是法国人Philippe Flajolet 教授发明的一种基数计数概率算法&#xff0c;每个 HyperLogLog 键只需要花费 12 KB 内存&#xff0c;就可以计算接近 2^64 个不同元素的基数。HyperLogLog 适用于大数据量的去重统计&#xff0c;HyperLogLog 提供不精确的去重计数方案…

基于Java+Swing实现餐厅点餐系统

基于JavaSwing实现餐厅点餐系统 一、系统介绍二、系统展示1.主页2.点菜3.下单4.结算5.销售情况&#xff08;管理员&#xff09; 三、系统实现四、其他系统五、获取源码 一、系统介绍 该系统针对两个方面的用户&#xff0c;一个是用餐客户&#xff0c;另一个是餐厅管理员。将功…

iOS 17 beta 2有哪些BUG?iOS 17 beta 2推荐升级吗?

虽然iOS 17 beta 2 带来了大量的功能更新&#xff0c;但毕竟是测试版&#xff0c;海量的适配BUG也一同随之而来。 想升级iOS 17 beta 2的用户不妨先查看下目前存在的问题汇总&#xff01; 一&#xff1a;存储空间更小了 升级beta1后存储空间缩小了大概3G左右&#xff0c;bet…

k8s网络通信

详解Kubernetes网络模型 Kubernetes 是为运行分布式集群而建立的&#xff0c;分布式系统的本质使得网络成为 Kubernetes 的核心和必要组成部分&#xff0c;了解 Kubernetes 网络模型可以使你能够正确运行、监控和排查应用程序故障。 网络所涉及的内容很多&#xff0c;拥有许多…

人人都能生成火爆全网的最不像二维码的二维码!

Sealos 公众号已接入了 GPT-4&#xff0c;完全免费&#xff01;欢迎前来调戏&#x1f447; 最近有人展示了使用 Stable Diffusion 创建的艺术二维码。这些二维码是使用定制训练的 ControlNet模型生成的。 但是操作门槛有点高。 你需要 GPU&#xff0c;还需要学习如何使用 Stabl…

diffusion model(二)—— DDIM技术小结

论文地址&#xff1a;Denoising Diffusion Implicit Models github地址&#xff1a;https://github.com/ermongroup/ddim 背景 去噪扩散概率模型 (DDPM1) 在没有对抗训练的情况下实现了高质量的图像生成&#xff0c;但其采样过程依赖马尔可夫假设&#xff0c;需要较多的时间…

SoapUI实践:自动化测试、压力测试、持续集成

因为项目的原因&#xff0c;前段时间研究并使用了 SoapUI 测试工具进行自测开发的 api。下面将研究的成果展示给大家&#xff0c;希望对需要的人有所帮助。 如果你想学习自动化测试&#xff0c;我这边给你推荐一套视频&#xff0c;这个视频可以说是B站播放全网第一的自动化测试…

Android View的渲染过程

原文链接 Android View的渲染过程 对于安卓开发猿来说&#xff0c;每天都会跟布局打交道&#xff0c;那么从我们写的一个布局文件&#xff0c;到运行后可视化的视图页面&#xff0c;这么长的时间内到底 发生了啥呢&#xff1f;今天我们就一起来探询这一旅程。 View tree的创建…

A Survey on In-context Learning

这是LLM系列相关综述文章的第二篇&#xff0c;针对《A Survey on In-context Learning》的翻译。 上下文学习综述 摘要1 引言2 概述3 定义和公式4 模型预热4.1 监督上下文训练4.2 半监督上下文训练 5 示例设计5.1 示例组织5.1.1 示例选择5.1.2 示例排序 5.2 示例形式化5.2.1 指…

Segment Anything Model Geospatial (SAM-Geo) 创建交互式地图

SAM-Geo是一个用于地理空间数据的Python 包&#xff0c;可在 PyPI 和 conda-forge 上使用。本节教程是SAM-Geo官网的一个教程&#xff0c;根据输入提示范围创建mask遮罩。后面还有一种基于提示词创建的方式&#xff0c;如只输出房屋、道路、树木等&#xff0c;下一期我们专门写…

IEEE Transactions的模板中,出现subfig包和fontenc包冲突的问题,怎么解决?

IEEE Transactions的模板中&#xff0c;出现subfig包和fontenc包冲突的问题&#xff0c;怎么解决&#xff1f; 本文章记录如何在IEEE Transactions的模板中&#xff0c;出现了subfig包和fontenc包冲突的问题&#xff0c;该怎么解决。 目录 IEEE Transactions的模板中&#xff…

ubuntu系统解压.rar文件问题与解决办法

ubuntu20.04解压rar文件 问题解决办法 问题 在ubuntu系统中&#xff0c;直接解压rar文件可能会报错&#xff0c;或者一直在提取文件中&#xff0c;无法结束。 例如直接右件该rar文件&#xff0c;将文件提取到此处 一直显示这个&#xff0c;无法结束 解决办法 需要安装一些软…