【算法题解】49. 二叉树的序列化与反序列化

news2024/9/19 10:40:05

这是一道 困难

https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/

文章目录

    • 题目
    • 深度优先搜索(前序遍历)
        • Java 代码实现
        • Go 代码实现
        • 复杂度分析
    • 广度优先搜索(层序遍历)
        • Java 代码实现
        • Go 代码实现
        • 复杂度分析
    • 后序遍历
        • Java 代码实现
        • Go 代码实现
        • 复杂度分析
    • 其他思路

题目

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。

请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

示例 1:

输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

示例 4:

输入:root = [1,2]
输出:[1,2]

提示:

  • 树中结点数在范围 [ 0 , 1 0 4 ] [0, 10^4] [0,104]
  • − 1000 < = N o d e . v a l < = 1000 -1000 <= Node.val <= 1000 1000<=Node.val<=1000

深度优先搜索(前序遍历)

通过常规前序遍历序列,我们可以直接知道根节点(即第一个节点),但是无法知道接下来的第二个节点是在根节点的左边还是右边。

如上图给定的前序序列 [1, 2, 3, 4, 5],我们只能明确根节点就是 1,接下来的 节点2 是在左边还是右边就无法确定了。

上图只画了其中的两种结果,实际上还有很多种其他可能。

既然常规前序序列无法确定后面的节点是在左边还有右边,我们就需要想办法对其进行增强。

其中一种方式就是补 null 值,不管是左边还是右边为空,序列化的时候统统补个 null。这样在反序列化的时候,只要遇到 null 就说明这条路径结束了。

如下图所示:

前序序列化结果为 "1,2,null,null,3,4,null,null,5,null,null"

反序列化的时候,第一个元素为根节点,接下来都是按照前序遍历的顺序,先走左边,直到遇到 null 结束,然后换另一边。
1.gif

序列化和反序列化都是递归的实现,其中序列化有做过对应的题目 【算法题解】42. 二叉树的前序遍历 。

反序列化见代码实现。

Java 代码实现

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        dfs(sb, root);
        return sb.toString();
    }

    private void dfs(StringBuilder sb, TreeNode root){
        if(sb.length() != 0){
            sb.append(",");
        }

        // 边界条件
        if (root == null){
            sb.append("null");
            return;
        }

        sb.append(root.val);
        dfs(sb, root.left);
        dfs(sb, root.right);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] vals = data.split(",");
        return decode(vals);
    }

    private int desIndex = 0;

    private TreeNode decode(String[] vals){
        String nodeVal = vals[desIndex++];
        // 边界条件
        if(nodeVal.equals("null")){
            return null;
        }

        TreeNode node = new TreeNode(Integer.valueOf(nodeVal));

        node.left = decode(vals);
        node.right = decode(vals);

        return node;
    }
}

// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// TreeNode ans = deser.deserialize(ser.serialize(root));

Go 代码实现

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */

type Codec struct {
    
}

func Constructor() Codec {
    return Codec{}
}

// Serializes a tree to a single string.
func (this *Codec) serialize(root *TreeNode) string {
    sb := &strings.Builder{}
    var dfs func(*TreeNode)
    dfs = func(node *TreeNode) {
        if len(sb.String()) != 0 {
            sb.WriteString(",")
        }
        if node == nil {
            sb.WriteString("null")
            return
        }
        sb.WriteString(strconv.Itoa(node.Val))
        dfs(node.Left)
        dfs(node.Right)
    }
    dfs(root)
    return sb.String()

}

// Deserializes your encoded data to tree.
func (this *Codec) deserialize(data string) *TreeNode {   
    vals := strings.Split(data, ",") 

    var decode func() *TreeNode

    decode = func() *TreeNode {
        nodeVal := vals[0]  
        vals = vals[1:]
        if nodeVal == "null" {
            return nil
        }

        Val, _ := strconv.Atoi(nodeVal)
        
        node := &TreeNode{Val, decode(), decode()}
        return node
    }

    return decode()
}


/**
 * Your Codec object will be instantiated and called as such:
 * ser := Constructor();
 * deser := Constructor();
 * data := ser.serialize(root);
 * ans := deser.deserialize(data);
 * 
 */

复杂度分析

时间复杂度 O ( N ) O(N) O(N)N 为二叉树的节点个数。其中序列化的时候需要遍历二叉树中的每一个节点,时间复杂度为 O ( N ) O(N) O(N)。反序列化的时候同样每个节点遍历一次,时间复杂度为 O ( N ) O(N) O(N)。总和为 O ( 2 N ) O(2N) O(2N),忽略常数后为 O ( N ) O(N) O(N)

空间复杂度 O ( N ) O(N) O(N)N 为二叉树的节点个数。序列化和反序列化的调用栈深度都是最大为 N。 序列化后生成的字符串数组,除了 null 值外剩余的就是 N 个节点的值。


广度优先搜索(层序遍历)

广度优先搜索的解法思路和深度优先搜索类似,都是在序列化的时候补 null 值。

以上图为例,序列化结果为:"1,2,3,null,null,4,5,null,null,null"

反序列化步骤为:

  1. 将序列化字符串转换为字符串数组,命名为 vals
  2. 第一个节点为根节点,如果是“null”,就直接返回 null,反序列化结束。否则就用第一个值创建根节点,并入队。此时 vals 的第一个值已使用,下一次将从第二个值取值,以此类推,每次取值都往后挪一位。
  3. 从队列中出队一个节点,然后分别从 vals 数组中取两个值用来构建左右节点。同样的,如果是“null”,就直接跳过;否则就用对应的值分别创建左右节点,并依次入队。
  4. 重复步骤3,直到队列为空。

Java 代码实现

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    private Queue<TreeNode> queue = new LinkedList<>();

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        
        queue.offer(root);
        while(!queue.isEmpty()){
            if(sb.length() != 0){
                sb.append(",");
            }

            TreeNode node = queue.poll();
            if(node == null){
                sb.append("null");
            }else{
                sb.append(node.val);
                queue.offer(node.left);
                queue.offer(node.right);
            }
        }
        return sb.toString();
    }


    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] vals = data.split(",");
        if(vals[0].equals("null")){
            return null;
        }
        
        TreeNode root = new TreeNode(Integer.valueOf(vals[0]));
        queue.offer(root);
        int index = 1;
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();

            String leftVal = vals[index++];
            if(!leftVal.equals("null")){
                TreeNode leftNode = new TreeNode(Integer.valueOf(leftVal));
                node.left = leftNode;
                queue.offer(leftNode);
            }

            String rightVal = vals[index++];
            if(!rightVal.equals("null")){
                TreeNode rightNode = new TreeNode(Integer.valueOf(rightVal));
                node.right = rightNode;
                queue.offer(rightNode);
            }
        }
        
        return root;
    }

}

// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// TreeNode ans = deser.deserialize(ser.serialize(root));

Go 代码实现

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */

type Codec struct {
    
}

func Constructor() Codec {
    return Codec{}
}

// Serializes a tree to a single string.
func (this *Codec) serialize(root *TreeNode) string {
    sb := &strings.Builder{}

    queue := []*TreeNode{root}

    for len(queue) > 0 {
        node := queue[0]
        queue = queue[1:]
        if node == nil {
            sb.WriteString("null,")
        }else {
            sb.WriteString(strconv.Itoa(node.Val))
            sb.WriteString(",")
            queue = append(queue, node.Left)
            queue = append(queue, node.Right)
        }
    }
    return sb.String()

}

// Deserializes your encoded data to tree.
func (this *Codec) deserialize(data string) *TreeNode {   
    vals := strings.Split(data, ",") 

    if vals[0] == "null" {
        return nil
    }

    Val, _ := strconv.Atoi(vals[0])
    vals = vals[1:]
    root := &TreeNode{Val, nil, nil}
    queue := []*TreeNode{root}
    
    for len(queue) > 0 {
        node := queue[0]
        queue = queue[1:]

        // 左节点
        if vals[0] != "null" {
            leftVal, _ := strconv.Atoi(vals[0])
            leftNode := &TreeNode{leftVal, nil, nil}
            node.Left = leftNode
            queue = append(queue, leftNode)
        }
        vals = vals[1:]

        // 右节点
        if vals[0] != "null" {
            rightVal, _ := strconv.Atoi(vals[0])
            rightNode := &TreeNode{rightVal, nil, nil}
            node.Right = rightNode
            queue = append(queue, rightNode)
        }
        vals = vals[1:]

    }
 

    return root
    
}


/**
 * Your Codec object will be instantiated and called as such:
 * ser := Constructor();
 * deser := Constructor();
 * data := ser.serialize(root);
 * ans := deser.deserialize(data);
 */

复杂度分析

时间复杂度 O ( N ) O(N) O(N)N 为二叉树的节点个数。其中序列化的时候需要遍历二叉树中的每一个节点,时间复杂度为 O ( N ) O(N) O(N)。反序列化的时候同样每个节点遍历一次,时间复杂度为 O ( N ) O(N) O(N)

空间复杂度 O ( N ) O(N) O(N)N 为二叉树的节点个数。序列化和反序列化时队列最大长度都是 N。序列化后生成的字符串数组,除了 null 值外剩余的就是 N 个节点的值。


后序遍历

和前序遍历(深度优先搜索)的思路一样,序列化的时候补 “null”。

不同的是反序列化的时候最后一个元素为根节点,然后倒着往前取值且先构建右节点,直到遇到“null”值结束。

Java 代码实现

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {


    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        dfs(sb, root);
        return sb.toString();
    }

    private void dfs(StringBuilder sb, TreeNode node){
        
        if(node == null){
            if(sb.length() != 0){
                sb.append(",");
            }
            sb.append("null");
            return;
        }

        dfs(sb, node.left);
        dfs(sb, node.right);
        sb.append(",");
        sb.append(node.val);
    }

    private int index = 0;
    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        String[] vals = data.split(",");

         // 最后一个是根节点
        index = vals.length - 1;
        return decode(vals);
    }

    private TreeNode decode(String[] vals){

        String nodeVal = vals[index--];
        if(nodeVal.equals("null")){
            return null;
        }

        TreeNode node = new TreeNode(Integer.valueOf(nodeVal));
        node.right = decode(vals);
        node.left = decode(vals);

        return node;

    }

}

// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// TreeNode ans = deser.deserialize(ser.serialize(root));

Go 代码实现

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */

type Codec struct {
    
}

func Constructor() Codec {
    return Codec{}
}

// Serializes a tree to a single string.
func (this *Codec) serialize(root *TreeNode) string {
    sb := &strings.Builder{}

    var dfs func(node *TreeNode)

    dfs = func(node *TreeNode){
        if node == nil {
            if len(sb.String()) != 0 {
                sb.WriteString(",")
            }
            sb.WriteString("null")
            return
        }
        dfs(node.Left)
        dfs(node.Right)
        sb.WriteString(",")
        sb.WriteString(strconv.Itoa(node.Val))
    } 
    dfs(root)
    return sb.String()

}

// Deserializes your encoded data to tree.
func (this *Codec) deserialize(data string) *TreeNode {   
    vals := strings.Split(data, ",") 
    var decode func() *TreeNode

    decode = func() *TreeNode {
        nodeVal := vals[len(vals) - 1]  
        vals = vals[:len(vals) - 1]
        if nodeVal == "null" {
            return nil
        }

        Val, _ := strconv.Atoi(nodeVal)
        
        node := &TreeNode{Val, nil, nil}
        node.Right = decode()
        node.Left = decode()
        return node
    }

    return decode()
    
}


/**
 * Your Codec object will be instantiated and called as such:
 * ser := Constructor();
 * deser := Constructor();
 * data := ser.serialize(root);
 * ans := deser.deserialize(data);
 */

复杂度分析

同前序遍历一样。

其他思路

通过 (前序 + 中序) 或者 (后序 + 中序) 来还原二叉树

前面解过 从前序与中序遍历序列构造二叉树 和 从中序与后序遍历序列构造二叉树 这两道题,通过 前序 + 中序 或者 后序 + 中序 可以构造出二叉树。

也就是说只要我们序列化的时候生成了 前序 + 中序 或者 后序 + 中序 这种结构的数据,那么我们就可以反序列化还原二叉树。

但是,上述这种解法要求二叉树中的节点必须没有重复项,因为我们需要通过中序遍历序列来划分左右子树,划分位置就是通过根节点来确定的。如果有另一个节点和根节点的值一样,那么我们就无法确认中序遍历中哪个是根节点,也就无法划分左右子树,从而也就无法还原二叉树了。

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

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

相关文章

容器的本质是什么

广义上来说&#xff0c;容器技术是动态的容器、静态的镜像和远端的仓库这三者的组合。 容器&#xff0c;就是一个特殊的隔离环境&#xff0c;它能够让进程只看到这个环境里的有限信息&#xff0c;不能对外界环境施加影响。 对于 Linux 操作系统来说&#xff0c;一个不受任何限…

【区块链】常见共识机制汇总

文章目录 公有链PoW - Proof of WorkPoS - Proof of StakeDPoS - Delegate Proof of StakePoA - Proof of Activity - 行动证明PoB - Proof of Burn 联盟链PaxosMulti-PaxosPBFTDDBFTIDBFTHotstuffZyzzyvaPoETCasperOurobrosPoSVMG-DPoSRPCAAlgorandTendermint 私有链 公有链 P…

用python实现扫雷游戏

前言 本人最近在学习python语言&#xff0c;发现python是一门很有意思的语音。python有大量的库&#xff0c;大量的函数&#xff0c;可以实现非常多的功能。尤其是在可视化方面&#xff0c;可以画图&#xff0c;可以弹出窗口。于是我就想着看能不能用python编写一个扫雷游戏。…

5. 创建声卡

代码位置 sound/soc/generic/simple-card.c static int asoc_simple_card_probe(struct platform_device *pdev) {...ret devm_snd_soc_register_card(dev, card);... } asoc_simple_card_probe -> devm_snd_soc_register_card -> snd_soc_register_card int snd_soc_r…

MS1825 SDK 移植指南

1. 概述 MS1825 SDK 支持以下 Macrosilicon 芯片&#xff0c;按照芯片功能组合的不同&#xff0c; SDK 中相关 API 和类型的定义有所 不同&#xff0c;请在该文档 API 和类型说明时特别关注&#xff1a; MS1825 SDK 的作用是帮助用户建立基于 MS1825 的视频输入输…

力扣 17. 电话号码的字母组合

题目来源&#xff1a;https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/ C题解&#xff1a; 递归法。 确定回溯函数参数&#xff1a;首先需要一个字符串s来收集叶子节点的结果&#xff0c;一个字符串数组result保存起来&#xff0c;定义为全局…

78-基于stm32单片机电压电流检测LCD1602显示(程序+原理图+元件清单全套资料)...

资料编号&#xff1a;078 功能介绍&#xff1a;采用stm32单片机作为主控CPU&#xff0c;采用精密电阻分压将高电压分压后接入STM32单片机ADC接口&#xff0c;采用ADC可以采集出当前的电压值&#xff0c;通过功率电阻来测量电路中的电流&#xff0c;通过串联电路电流相同的原理&…

正则表达式 教程与简介 | 一看就懂!!!(一)

目录 一、正则表达式 - 教程 二、 正则表达式的模式 &#xff08;一&#xff09;字面值字符 &#xff08;二&#xff09;特殊字符 &#xff08;三&#xff09;字符类 &#xff08;四&#xff09;元字符 &#xff08;五&#xff09;量词 &#xff08;六&#xff09;边界…

如何进行小红书笔记关键词布局,热词分析!

坐拥2.6亿活跃用户&#xff0c;小红书已经成为品牌宣推、种草的重要平台之一。那么品牌进入平台&#xff0c;如何进行小红书笔记关键词布局&#xff0c;热词分析&#xff01; 一、 如何确定关键词 想要做好小红书关键词布局&#xff0c;首先要明确如何确定关键词。 1、当我们要…

shell判断程序是否运行,守护进程

一、需求 服务部署在linux上&#xff0c;要求服务器上的服务可以一直保持正常运行 二、问题 在linux上部署的微服务&#xff0c;不知道什么原因过一段时间就自己停掉了&#xff0c;无法启动。 三、解决办法 添加angle守护进程&#xff0c;通过定时执行脚本来判断程序是否运行…

为什么对ChatGPT、ChatGLM这样的大语言模型说“你是某某领域专家”,它的回答会有效得多?(一)...

“ 太长不看总结版&#xff1a;LLM大模型的本质在于计算某个词汇后面应该跟着哪些词汇的概率。当问题给定了特定的限定范围后&#xff0c;它能够找到一条相对明确的计算路径&#xff0c;从一系列概率分布中挑选出所需的答案。否则&#xff0c;它会根据最常见且最高概率的组合方…

浑元太极马老师和小薇-UMLChina建模知识竞赛第4赛季第7轮[更新]

DDD领域驱动设计批评文集 欢迎加入“软件方法建模师”群 《软件方法》各章合集 参考潘加宇在《软件方法》和UMLChina公众号文章中发表的内容作答。在本文下留言回答。 第7轮一直无人得分&#xff0c;再次更换题目。 因有的题目之前已经出过&#xff0c;本轮需要最先答对全…

聊天室(二)__ unipush 推送如何实现?

你想搞个自己的聊天室 app 吗&#xff1f;好多前端同学会好奇聊天室app的推送是怎么搞的&#xff1f;今天就以前端同学使用最多的 uniapp 开发的 app 推送为例&#xff0c;悄悄告诉大家推送是如何实现的&#xff01; 项目技术栈&#xff1a; 项目基于 vue3 的 uniapp 推送基于…

从小白到大神之路之学习运维第51天---第三阶段----redis高可用集群数据库的安装部署

第三阶段基础 时 间&#xff1a;2023年7月3日 参加人&#xff1a;全班人员 内 容&#xff1a; 生产级redis cluster部署 目录 一、环境配置&#xff1a;【两台服务器】 二、redis多实例配置&#xff1a; 三、构建redis cluster集群 四、生产集群部署 五、集群故障切…

Makefile:1: *** 遗漏分隔符 (null)。 停止。解决方法

在使用ubuntu时&#xff0c;make命令后&#xff0c;直接弹出了个错误。。。。。。 Makefile: n n表示出问题的行数&#xff0c;仔细检查代码的内容&#xff0c;主要问题可能是该有的空格是否添加或者使用$引入的参数是否存在等 我的问题是这个 ifneq 后面的空格没有加 耐心查…

论文浅尝 | 大语言模型综述

笔记整理&#xff1a;刘康为、方润楠&#xff0c;浙江大学硕士&#xff0c;研究方向为自然语言处理 链接&#xff1a;https://arxiv.org/abs/2303.18223 一、介绍 在当前机遇和挑战的背景下&#xff0c;对大语言模型的研究和开发需要更多的关注。为了让读者对大语言模型有一个基…

C++实现打包工具代码框架(附源码)

C++常用功能源码系列 文章目录 C++常用功能源码系列前言一、打包工具二、packtool框架前言 本文是C/C++常用功能代码封装专栏的导航贴。部分来源于实战项目中的部分功能提炼,希望能够达到你在自己的项目中拿来就用的效果,这样更好的服务于工作实践。 专栏介绍:专栏讲本人近…

windows10安装wsl2,centos内核

windows10安装wsl2&#xff0c;centos内核 检查系统环境 必须运行 Windows 10 版本 2004 及更高版本&#xff08;内部版本 19041 及更高版本&#xff09;或 Windows 11 才能使用以下命令。 一、开启WSL2特性 【控制面板】>>【程序】>>【程序和功能】>>【启…

apple pencil平替笔哪个好用?适用于绘画的电容笔推荐

由于ipad的版本一直在升级&#xff0c;其功能也在增加&#xff0c;其功能已经达到了与手提电脑相媲美的程度。而且随着科技的发展&#xff0c;ipad也不仅仅是一个娱乐的工具&#xff0c;更是一个可以用来学习、画画、工作的强大工具。想要提高生产力&#xff0c;那么电容笔就是…

mysql 8.0版本更换用户密码

1、首先 cmd 进入命令行 mysql -uroot -p 2、查询版本号 select version(); 3、看一下数据库 show databases; 4、使用mysql即可 5、进行查询 user、host select user,host from user; 6、修改root的密码 alter user root% identified by 1234; 7、刷新权限 flush privi…