LC 剑指 Offer 37. 序列化二叉树

题目描述

这是 LeetCode 上的 剑指 Offer 37. 序列化二叉树 ,难度为 困难

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

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

提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。

示例 1:

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

示例 2:
1
2
输入:root = []
输出:[]

示例 3:
1
2
输入:root = [1]
输出:[1]

示例 4:
1
2
输入:root = [1,2]
输出:[1,2]

提示:

  • 树中结点数在范围 $[0, 10^4]$ 内
  • -1000 <= Node.val <= 1000

基本思路

无论使用何种「遍历方式」进行二叉树存储,为了方便,我们都需要对空节点有所表示。

其实题目本身的样例就给我们提供了很好的思路:使用层序遍历的方式进行存储,对于某个叶子节点的空节点进行存储,同时确保不递归存储空节点对应的子节点。


层序遍历

根据节点值的数据范围 -1000 <= Node.val <= 1000(我是在 297. 二叉树的序列化与反序列化 看的,你也可以不使用数字,使用某个特殊字符进行表示,只要能在反序列时有所区分即可),我们可以建立占位节点 emptyNode 用来代指空节点(emptyNode.val = INF)。

序列化:先特判掉空树的情况,之后就是常规的层序遍历逻辑:

  1. 起始时,将 root 节点入队;
  2. 从队列中取出节点,检查节点是否有左/右节点:
    • 如果有的话,将值追加序列化字符中(注意使用分隔符),并将节点入队;
    • 如果没有,检查当前节点是否为 emptyNode 节点,如果不是 emptyNode 说明是常规的叶子节点,需要将其对应的空节点进行存储,即将 emptyNode 入队;
  3. 循环流程 $2$,直到整个队列为空。

反序列:同理,怎么「序列化」就怎么进行「反序列」即可:

  1. 起始时,构造出 root 并入队;
  2. 每次从队列取出元素时,同时从序列化字符中截取两个值(对应左右节点),检查是否为 INF,若不为 INF 则构建对应节点;
  3. 循环流程 $2$,直到整个序列化字符串被处理完(注意跳过最后一位分隔符)。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Codec {
int INF = -2000;
TreeNode emptyNode = new TreeNode(INF);
public String serialize(TreeNode root) {
if (root == null) return "";

StringBuilder sb = new StringBuilder();
Deque<TreeNode> d = new ArrayDeque<>();
d.addLast(root);
while (!d.isEmpty()) {
TreeNode poll = d.pollFirst();
sb.append(poll.val + "_");
if (!poll.equals(emptyNode)) {
d.addLast(poll.left != null ? poll.left : emptyNode);
d.addLast(poll.right != null ? poll.right : emptyNode);
}
}
return sb.toString();
}

public TreeNode deserialize(String data) {
if (data.equals("")) return null;

String[] ss = data.split("_");
int n = ss.length;
TreeNode root = new TreeNode(Integer.parseInt(ss[0]));
Deque<TreeNode> d = new ArrayDeque<>();
d.addLast(root);
for (int i = 1; i < n - 1; i += 2) {
TreeNode poll = d.pollFirst();
int a = Integer.parseInt(ss[i]), b = Integer.parseInt(ss[i + 1]);
if (a != INF) {
poll.left = new TreeNode(a);
d.addLast(poll.left);
}
if (b != INF) {
poll.right = new TreeNode(b);
d.addLast(poll.right);
}
}
return root;
}
}

  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(n)$

最后

这是我们「刷穿 LeetCode」系列文章的第 剑指 Offer 37 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!