第六章 - 高频考题(困难)
0297. 二叉树的序列化与反序列化

题目地址(297. 二叉树的序列化与反序列化)

题目描述

1
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
2
3
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
4
5
示例:
6
7
你可以将以下二叉树:
8
9
1
10
/ \
11
2 3
12
/ \
13
4 5
14
15
序列化为 "[1,2,3,null,null,4,5]"
16
提示: 这与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
17
18
说明: 不要使用类的成员 / 全局 / 静态变量来存储状态,你的序列化和反序列化算法应该是无状态的。
Copied!

思路(BFS)

如果我将一个二叉树的完全二叉树形式序列化,然后通过 BFS 反序列化,这不就是力扣官方序列化树的方式么?比如:
1
1
2
/ \
3
2 3
4
/ \
5
4 5
Copied!
序列化为 "[1,2,3,null,null,4,5]"。 这不就是我刚刚画的完全二叉树么?就是将一个普通的二叉树硬生生当成完全二叉树用了。
其实这并不是序列化成了完全二叉树,下面会纠正。
将一颗普通树序列化为完全二叉树很简单,只要将空节点当成普通节点入队处理即可。代码:
1
class Codec:
2
3
def serialize(self, root):
4
q = collections.deque([root])
5
ans = ''
6
while q:
7
cur = q.popleft()
8
if cur:
9
ans += str(cur.val) + ','
10
q.append(cur.left)
11
q.append(cur.right)
12
else:
13
# 除了这里不一样,其他和普通的不记录层的 BFS 没区别
14
ans += 'null,'
15
# 末尾会多一个逗号,我们去掉它。
16
return ans[:-1]
Copied!
细心的同学可能会发现,我上面的代码其实并不是将树序列化成了完全二叉树,这个我们稍后就会讲到。另外后面多余的空节点也一并序列化了。这其实是可以优化的,优化的方式也很简单,那就是去除末尾的 null 即可。
你只要彻底理解我刚才讲的我们可以给完全二叉树编号,这样父子之间就可以通过编号轻松求出。比如我给所有节点从左到右从上到下依次从 1 开始编号。那么已知一个节点的编号是 i,那么其左子节点就是 2 * i,右子节点就是 2 * 1 + 1,父节点就是 (i + 1) / 2。 这句话,那么反序列化对你就不是难事。
如果我用一个箭头表示节点的父子关系,箭头指向节点的两个子节点,那么大概是这样的:
我们刚才提到了:
  • 1 号节点的两个子节点的 2 号 和 3 号。
  • 2 号节点的两个子节点的 4 号 和 5 号。
  • 。。。
  • i 号节点的两个子节点的 2 * i 号 和 2 * 1 + 1 号。
此时你可能会写出类似这样的代码:
1
def deserialize(self, data):
2
if data == 'null': return None
3
nodes = data.split(',')
4
root = TreeNode(nodes[0])
5
# 从一号开始编号,编号信息一起入队
6
q = collections.deque([(root, 1)])
7
while q:
8
cur, i = q.popleft()
9
# 2 * i 是左节点,而 2 * i 编号对应的其实是索引为 2 * i - 1 的元素, 右节点同理。
10
if 2 * i - 1 < len(nodes): lv = nodes[2 * i - 1]
11
if 2 * i < len(nodes): rv = nodes[2 * i]
12
if lv != 'null':
13
l = TreeNode(lv)
14
# 将左节点和 它的编号 2 * i 入队
15
q.append((l, 2 * i))
16
cur.left = l
17
if rv != 'null':
18
r = TreeNode(rv)
19
# 将右节点和 它的编号 2 * i + 1 入队
20
q.append((r, 2 * i + 1))
21
cur.right = r
22
23
return root
Copied!
但是上面的代码是不对的,因为我们序列化的时候其实不是完全二叉树,这也是上面我埋下的伏笔。因此遇到类似这样的 case 就会挂:
这也是我前面说”上面代码的序列化并不是一颗完全二叉树“的原因。
其实这个很好解决, 核心还是上面我画的那种图:
其实我们可以:
  • 用三个指针分别指向数组第一项,第二项和第三项(如果存在的话),这里用 p1,p2,p3 来标记,分别表示当前处理的节点,当前处理的节点的左子节点和当前处理的节点的右子节点。
  • p1 每次移动一位,p2 和 p3 每次移动两位。
  • p1.left = p2; p1.right = p3。
  • 持续上面的步骤直到 p1 移动到最后。
因此代码就不难写出了。反序列化代码如下:
1
def deserialize(self, data):
2
if data == 'null': return None
3
nodes = data.split(',')
4
root = TreeNode(nodes[0])
5
q = collections.deque([root])
6
i = 0
7
while q and i < len(nodes) - 2:
8
cur = q.popleft()
9
lv = nodes[i + 1]
10
rv = nodes[i + 2]
11
i += 2
12
if lv != 'null':
13
l = TreeNode(lv)
14
q.append(l)
15
cur.left = l
16
if rv != 'null':
17
r = TreeNode(rv)
18
q.append(r)
19
cur.right = r
20
21
return root
Copied!
这个题目虽然并不是完全二叉树的题目,但是却和完全二叉树很像,有借鉴完全二叉树的地方。

代码

  • 代码支持:JS,Python, Go
JS Code:
1
const serialize = (root) => {
2
const queue = [root];
3
let res = [];
4
while (queue.length) {
5
const node = queue.shift();
6
if (node) {
7
res.push(node.val);
8
queue.push(node.left);
9
queue.push(node.right);
10
} else {
11
res.push("#");
12
}
13
}
14
return res.join(",");
15
};
16
17
const deserialize = (data) => {
18
if (data == "#") return null;
19
20
const list = data.split(",");
21
22
const root = new TreeNode(list[0]);
23
const queue = [root];
24
let cursor = 1;
25
26
while (cursor < list.length) {
27
const node = queue.shift();
28
29
const leftVal = list[cursor];
30
const rightVal = list[cursor + 1];
31
32
if (leftVal != "#") {
33
const leftNode = new TreeNode(leftVal);
34
node.left = leftNode;
35
queue.push(leftNode);
36
}
37
if (rightVal != "#") {
38
const rightNode = new TreeNode(rightVal);
39
node.right = rightNode;
40
queue.push(rightNode);
41
}
42
cursor += 2;
43
}
44
return root;
45
};
Copied!
Python Code:
1
# Definition for a binary tree node.
2
# class TreeNode(object):
3
# def __init__(self, x):
4
# self.val = x
5
# self.left = None
6
# self.right = None
7
8
class Codec:
9
def serialize(self, root):
10
ans = ''
11
queue = [root]
12
while queue:
13
node = queue.pop(0)
14
if node:
15
ans += str(node.val) + ','
16
queue.append(node.left)
17
queue.append(node.right)
18
else:
19
ans += '#,'
20
print(ans[:-1])
21
return ans[:-1]
22
23
24
25
def deserialize(self, data: str):
26
if data == '#': return None
27
nodes = data.split(',')
28
if not nodes: return None
29
root = TreeNode(nodes[0])
30
queue = [root]
31
# 已经有 root 了,因此从 1 开始
32
i = 1
33
34
while i < len(nodes) - 1:
35
node = queue.pop(0)
36
lv = nodes[i]
37
rv = nodes[i + 1]
38
i += 2
39
if lv != '#':
40
l = TreeNode(lv)
41
node.left = l
42
queue.append(l)
43
44
if rv != '#':
45
r = TreeNode(rv)
46
node.right = r
47
queue.append(r)
48
return root
Copied!
Go Code:
1
/**
2
* Definition for a binary tree node.
3
* type TreeNode struct {
4
* Val int
5
* Left *TreeNode
6
* Right *TreeNode
7
* }
8
*/
9
10
type Codec struct {
11
}
12
13
func Constructor() Codec {
14
return Codec{}
15
}
16
17
// Serializes a tree to a single string.
18
func (this *Codec) serialize(root *TreeNode) string {
19
ans := ""
20
q := []*TreeNode{root} // queue
21
var cur *TreeNode
22
for len(q) > 0 {
23
cur, q = q[0], q[1:]
24
if cur != nil {
25
ans += strconv.Itoa(cur.Val) + ","
26
q = append(q, cur.Left)
27
q = append(q, cur.Right)
28
} else {
29
ans += "#,"
30
}
31
}
32
return ans[:len(ans)-1]
33
}
34
35
// Deserializes your encoded data to tree.
36
func (this *Codec) deserialize(data string) *TreeNode {
37
if data == "#" {
38
return nil
39
}
40
41
a := strings.Split(data, ",")
42
var s string
43
s, a = a[0], a[1:]
44
v, _ := strconv.Atoi(s)
45
root := &TreeNode{Val: v}
46
q := []*TreeNode{root} // queue
47
var cur, newNode *TreeNode
48
for len(a) > 0 {
49
cur, q = q[0], q[1:] // pop
50
51
s, a = a[0], a[1:] // 左子树
52
if s != "#" {
53
v, _ := strconv.Atoi(s)
54
newNode = &TreeNode{Val: v}
55
cur.Left = newNode
56
q = append(q, newNode)
57
}
58
59
if len(a) == 0 {
60
return root
61
}
62
63
s, a = a[0], a[1:] // 右子树
64
if s != "#" {
65
v, _ := strconv.Atoi(s)
66
newNode = &TreeNode{Val: v}
67
cur.Right = newNode
68
q = append(q, newNode)
69
}
70
}
71
return root
72
}
Copied!
复杂度分析
  • 时间复杂度:$O(N)$,其中 N 为树的节点数。
  • 空间复杂度:$O(Q)$,其中 Q 为队列长度,最坏的情况是满二叉树,此时和 N 同阶,其中 N 为树的节点总数