【面试经典 150 | 二叉树】二叉树展开为链表

文章目录

  • 写在前面
  • Tag
  • 题目来源
  • 解题思路
    • 方法一:前序遍历
    • 方法二:同步进行
    • 方法三:原地操作
  • 写在最后

写在前面

本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……

专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:

  • Tag:介绍本题牵涉到的知识点、数据结构;
  • 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
  • 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
  • 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
  • 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。

Tag

【二叉树】【前序遍历】【原地操作】


题目来源

114. 二叉树展开为链表


解题思路

方法一:前序遍历

一个朴素的解法是对二叉树进行前序遍历并记录节点到节点数组中,然后将节点数组中的每个节点的左子节点指向空节点,右子节点指向指向下一个节点。

代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:  
    void flatten(TreeNode* root) {
        vector<TreeNode*> tmp;
        preorder(root, tmp);
        int n = tmp.size();
        for(int i = 1; i < n; ++i) {
            TreeNode *pre = tmp.at(i-1), *curr = tmp.at(i);
            pre->left = nullptr;
            pre->right = curr;
        }
    }

    void preorder(TreeNode* root, vector<TreeNode*>& tmp) {
        if(root == nullptr) {
            return;
        }
        tmp.push_back(root);
        preorder(root->left, tmp);
        preorder(root->right, tmp);
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 是二叉树的节点数。前序遍历的时间复杂度为 O ( n ) O(n) O(n),更新每个节点的子节点操作时间复杂度也是 O ( n ) O(n) O(n)

空间复杂度: O ( n ) O(n) O(n)。递归进行前序遍历需要使用栈,空间复杂度为 O ( n ) O(n) O(n),节点数组的占用空间也为线性空间。

方法二:同步进行

方法一中是先将二叉树前序遍历,而后展开(更节点数组中的节点的子节点),能不能同时进行呢?

在方法一中前序之后,直接展开会破坏二叉树的结构从而丢失子节点的信息,之所以会丢失是因为在对左子树进行遍历时没有存储右子节点的信息,在遍历完左子树之后才能拿个获得右子节点的信息。

现在只要我们在遍历左子树之前就先获得右子树的信息,并入栈中,子节点的信息就不会丢失,就可以实现前序遍历和展开的同时进行。

代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void flatten(TreeNode* root) {
        if (!root) {
            return;
        }
        stack<TreeNode*> stk;
        stk.push(root);
        TreeNode* prev = nullptr;
        while (!stk.empty()) {
            TreeNode* cur = stk.top(); stk.pop();
            if (prev) {
                prev->left = nullptr;
                prev->right = cur;
            }
            if (cur->right) {
                stk.push(cur->right);
            }
            if (cur->left) {
                stk.push(cur->left);
            }
            prev = cur;
        }
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 是二叉树的节点数。前序遍历的时间复杂度为 O ( n ) O(n) O(n),更新每个节点的子节点操作时间复杂度也是 O ( n ) O(n) O(n)

空间复杂度: O ( n ) O(n) O(n)。递归进行前序遍历需要使用栈,空间复杂度为 O ( n ) O(n) O(n),节点数组的占用空间也为线性空间。

方法三:原地操作

前面两种方法都需要使用栈来存储节点,那有没有不使用额外空间的解法呢?

我们仔细分析一下:

  • 如果一个节点的左子节点为空,那么该节点不需要进行展开操作。
  • 如果一个节点的左子节点不为空,则该节点的左子树中最后一个节点被访问之后,该节点的右子节点被访问。该节点的左子树中最后一个被访问的节点是左子树中的最右边的节点,也是该节点的前驱节点。因此,问题转化成寻找当前节点的前驱节点。

具体做法是,对于当前节点,如果其左子节点不为空,则在其左子树中找到最右边的节点,作为前驱节点。将当前节点的右子节点赋给前驱节点的右子节点,然后将当前节点的左子节点赋给当前节点的右子节点,并将当前节点的左子节点设为空。对当前节点处理结束后,继续处理链表中的下一个节点,直到所有节点都处理结束。

代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void flatten(TreeNode* root) {
        TreeNode* cur = root;
        while (cur) {
            if (cur->left) {
                TreeNode* next = cur->left;
                TreeNode* pre = next;
                while (pre->right) {
                    pre = pre->right;
                }
                pre->right = cur->right;
                cur->left = nullptr;
                cur->right = next;
            }
            cur = cur->right;
        }
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 是二叉树的节点数。展开为单链表的过程中,需要对每个节点访问一次,在寻找前驱节点的过程中,每个节点最多被额外访问一次。

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


写在最后

如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度的方法,欢迎评论区交流。

最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图像处理的基本操作

一、PyCharm中安装OpenCV模块 二、读取图像 1、基本语法 OpenCV提供了用于读取图像的imread()方法&#xff0c;其语法如下&#xff1a; image cv2.imread&#xff08;filename&#xff0c;flags&#xff09; &#xff08;1&#xff09;image&#xff1a;是imread方法的返回…

opencv可视化图片-----c++

可视化图片 #include <opencv2/opencv.hpp> #include <opencv2/core.hpp> #include <filesystem>// 将数据类型转换为字符串 std::string opencvTool::type2str(int type) {std::string r;uchar depth type & CV_MAT_DEPTH_MASK;uchar chans 1 (typ…

机械臂模型更换成自己的urdf模块

1.将urdf生成slx文件 smimport(rm_65_flange.urdf);%生成Simscape物理模型 2.更换joint部分&#xff08;对应与几个输入几个输出&#xff09;&#xff08;依次更换&#xff09; 3.更改关节部分&#xff08;依次更换&#xff09; 找到urdf文件夹下的meshes文件夹&#xff0c;看…

Python-VBA函数之旅-issubclass函数

目录 一、issubclass函数的常见应用场景&#xff1a; 二、issubclass函数使用注意事项&#xff1a; 三、如何用好issubclass函数&#xff1f; 1、issubclass函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff…

spark3.0.0单机模式安装

注&#xff1a;此安装教程基于hadoop3集群版本 下载安装包 下载spark3.0.0版本&#xff0c;hadoop和spark版本要对应&#xff0c;否则会不兼容 用xftp上传Linux虚拟机&#xff0c;上传目录/bigdata&#xff08;可修改&#xff09; 解压 tar -zxvf /bigdata/spark-3.0.0-bin-h…

rust是否可以用于8051单片机开发工作?

目前&#xff0c;Rust 在嵌入式领域的发展主要集中在一些常见的架构上&#xff0c;如ARM Cortex-M&#xff08;包括STM32系列&#xff09;、RISC-V等。我这里有一套嵌入式入门教程&#xff0c;不仅包含了详细的视频 讲解&#xff0c;项目实战。如果你渴望学习嵌入式&#xff0c…

java的各种锁

我们先来看看有什么锁 一、java锁 1、乐观锁 乐观锁 是一种乐观思想 &#xff0c;假定当前环境是读多写少&#xff0c;遇到并发写的概率比较低&#xff0c;读数 据时认为别的线程不会正在进行修改&#xff08;所以没有上锁&#xff09;。写数据时&#xff0c;判断当前 与期望…

【3GPP】【核心网】【5G】5G核心网协议解析(四)(超详细)

1. 欢迎大家订阅和关注&#xff0c;精讲3GPP通信协议&#xff08;2G/3G/4G/5G/IMS&#xff09;知识点&#xff0c;专栏会持续更新中.....敬请期待&#xff01; 目录 1. NGAP 按流程功能分类 1.1 接口管理过程 1.1.1 NG Setup 1.2.1 NAS消息传输过程 Transport of NAS Messa…

.NET 基于Socket中转WebSocket

前言 针对IOS App Proxy Server无法直连WebSocket&#xff0c;建立 Socket中转端。 WebSocket 端&#xff1a; WebSocket 端用于实现实时通信功能。 WebSocket 端通过 WebSocket 协议与中转端通信&#xff0c;中转端可以通过 WebSocket 或其他传输协议与 WebSocket 端建立连…

【工具】录屏软件Captura安装使用及ffmpeg下载配置

开启技术视频创作&#xff0c;录屏软件林林总总&#xff0c;适合的、习惯的最好。 录屏软件Captura的使用及ffmpeg下载配置 1.Captura下载、安装2.FFmpeg下载、配置3.Captura屏幕录制试用、录制视频效果 1.Captura下载、安装 Captura主要是一个免费开源的录屏软件&#xff0c…

2024年新算法-鹦鹉优化器(PO)优化BP神经网络回归预测

2024年新算法-鹦鹉优化器(PO)优化BP神经网络回归预测 亮点&#xff1a; 输出多个评价指标&#xff1a;R2&#xff0c;RMSE&#xff0c;MSE&#xff0c;MAPE和MAE 满足需求&#xff0c;分开运行和对比的都有对应的主函数&#xff1a;main_BP, main_PO, main_BPvsBP_PO&#x…

洛谷 P1021 邮票面值设计

原题链接&#xff1a;[NOIP1999 提高组] 邮票面值设计 - 洛谷 目录 题目描述 解题思路&#xff1a; 代码实现&#xff1a; 题后总结&#xff1a; 题目描述 给定一个信封&#xff0c;最多只允许粘贴 N 张邮票&#xff0c;计算在给定 K&#xff08;NK≤15&#xff09;种邮票…

javaWeb项目-邮票鉴赏系统功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、Java技术 Java 程…

spring boot3单模块项目工程搭建-下(个人开发模板)

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途 目录 写在前面 上文衔接 常用依赖介绍以及整合 web组件 测试组件 样板代码生成 数据库连接器 常用工具包 面向切面编…

浅涉ROS世界中的坐标系及其他

声明&#xff1a;文中图片素材均采用了其他博主文章&#xff08;文末参考来源&#xff09;&#xff0c;如有侵权或不妥&#xff08;确有不妥和不安&#xff0c;奈何苦于佳图难觅&#xff09;&#xff0c;还望告知&#xff0c;立即删除&#xff01; 坐标系统 ROS中的…

【Stable Diffusion系列】(一):AI绘画本地部署教程

目录 一、总览 二、本地部署 1、安装cuda 2、安装python 3、安装git 4、方法一 1&#xff09;获取安装包 2&#xff09;update 3&#xff09;run 5、方法二 1&#xff09;git clone 2&#xff09;双击webui-user.bat 3&#xff09;更新 6、设置启动参数 7、…

【linux】进程地址被占用

在强制关闭一个udp程序后&#xff0c;重启该程序报错&#xff1a; bind error: Address already in use 查找并关闭占用端口的进程&#xff1a; 首先&#xff0c;确定哪个进程占用了目标端口。在Linux系统中&#xff0c;可以使用以下命令&#xff1a; netstat -tulnp | grep …

ArcGIS无法开始编辑TIN!开始编辑TIN显示灰色

ArcGIS无法开始编辑TIN&#xff01;开始编辑TIN显示灰色&#xff1f; 解决方案&#xff01; 1、确认自定义——扩展模块中空间分析、3D分析模块勾选。 2、确认以上后&#xff0c;还是不能编辑的话&#xff0c;我们可以调出 3D分析分析工具条&#xff0c;你就会发现。TIN编辑工…

Paddle 1.8 与 Paddle 2.0 API 映射表

安装2.6的paddlepaddle之后总是报fluid的错误&#xff0c;查询得知这个接口已经弃用了&#xff0c;但是一直找不到替换接口&#xff0c;偶然查询报错信息的时候找到了映射表&#xff0c;转存一下。 Paddle 1.8 与 Paddle 2.0 API 映射表

在React函数组件中使用错误边界和errorElement进行错误处理

在React 18中,函数组件可以使用两种方式来处理错误: 使用 ErrorBoundary ErrorBoundary 是一种基于类的组件,可以捕获其子组件树中的任何 JavaScript 错误,并记录这些错误、渲染备用 UI 而不是冻结的组件树。 在函数组件中使用 ErrorBoundary,需要先创建一个基于类的 ErrorB…