Skip to content

Commit 3406d85

Browse files
committed
297
1 parent 608726b commit 3406d85

File tree

2 files changed

+361
-0
lines changed

2 files changed

+361
-0
lines changed

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,5 @@
241241
* [290. Word Pattern](leetcode-290-Word-Pattern.md)
242242
* [292*. Nim Game](leetcode-292-Nim-Game.md)
243243
* [295*. Find Median from Data Stream](leetcode-295-Find-Median-from-Data-Stream.md)
244+
* [297. Serialize and Deserialize Binary Tree](leetcode-297-Serialize-and-Deserialize-Binary-Tree.md)
244245
* [更多](more.md)
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
# 题目描述(简单难度)
2+
3+
297、Serialize and Deserialize Binary Tree
4+
5+
Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.
6+
7+
Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.
8+
9+
**Example:**
10+
11+
```
12+
You may serialize the following tree:
13+
14+
1
15+
/ \
16+
2 3
17+
/ \
18+
4 5
19+
20+
as "[1,2,3,null,null,4,5]"
21+
```
22+
23+
**Clarification:** The above format is the same as [how LeetCode serializes a binary tree](https://leetcode.com/faq/#binary-tree). You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself.
24+
25+
**Note:** Do not use class member/global/static variables to store states. Your serialize and deserialize algorithms should be stateless.
26+
27+
提供两个方法,一个方法将二叉树序列化为一个字符串,另一个方法将序列化的字符串还原为二叉树。
28+
29+
# 解法一
30+
31+
来个偷懒的方法,我们知道通过先序遍历和中序遍历可以还原一个二叉树。[144 题](https://leetcode.wang/leetcode-144-Binary-Tree-Preorder-Traversal.html) 的先序遍历,[94 题](https://leetcode.wang/leetCode-94-Binary-Tree-Inorder-Traversal.html) 的中序遍历,[105 题](https://leetcode.wang/leetcode-105-Construct-Binary-Tree-from-Preorder-and-Inorder-Traversal.html) 的通过先序遍历和中序遍历还原二叉树。
32+
33+
上边主要的代码都有了,我们还需要将先序遍历和中序遍历的结果转为字符串,以及从字符串还原先序遍历和中序遍历的结果。
34+
35+
`list``toString` 方法,它会把 `list` 转成 `"[1, 2, 3, 5]"`这样类似的字符串。所以把这个字符串还原为 `list` 的时候,我们只需要去掉首尾的中括号,然后用逗号切割即可。
36+
37+
```java
38+
// Encodes a tree to a single string.
39+
public String serialize(TreeNode root) {
40+
if (root == null) {
41+
return "";
42+
}
43+
List<Integer> preOrder = preorderTraversal(root);
44+
List<Integer> inOrder = inorderTraversal(root);
45+
//两个结果用 "@" 分割
46+
return preOrder + "@" + inOrder;
47+
}
48+
49+
// Decodes your encoded data to tree.
50+
public TreeNode deserialize(String data) {
51+
if (data.length() == 0) {
52+
return null;
53+
}
54+
String[] split = data.split("@");
55+
//还原先序遍历的结果
56+
String[] preStr = split[0].substring(1, split[0].length() - 1).split(",");
57+
int[] preorder = new int[preStr.length];
58+
for (int i = 0; i < preStr.length; i++) {
59+
//trim 是为了去除首尾多余的空格
60+
preorder[i] = Integer.parseInt(preStr[i].trim());
61+
}
62+
63+
//还原中序遍历的结果
64+
String[] inStr = split[1].substring(1, split[1].length() - 1).split(",");
65+
int[] inorder = new int[inStr.length];
66+
for (int i = 0; i < inStr.length; i++) {
67+
inorder[i] = Integer.parseInt(inStr[i].trim());
68+
}
69+
70+
return buildTree(preorder, inorder);
71+
}
72+
73+
// 前序遍历
74+
public List<Integer> preorderTraversal(TreeNode root) {
75+
List<Integer> list = new ArrayList<>();
76+
preorderTraversalHelper(root, list);
77+
return list;
78+
}
79+
80+
private void preorderTraversalHelper(TreeNode root, List<Integer> list) {
81+
if (root == null) {
82+
return;
83+
}
84+
list.add(root.val);
85+
preorderTraversalHelper(root.left, list);
86+
preorderTraversalHelper(root.right, list);
87+
}
88+
89+
// 中序遍历
90+
public List<Integer> inorderTraversal(TreeNode root) {
91+
List<Integer> ans = new ArrayList<>();
92+
getAns(root, ans);
93+
return ans;
94+
}
95+
96+
private void getAns(TreeNode node, List<Integer> ans) {
97+
if (node == null) {
98+
return;
99+
}
100+
getAns(node.left, ans);
101+
ans.add(node.val);
102+
getAns(node.right, ans);
103+
}
104+
105+
//还原二叉树
106+
private TreeNode buildTree(int[] preorder, int[] inorder) {
107+
return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length);
108+
}
109+
110+
private TreeNode buildTreeHelper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
111+
// preorder 为空,直接返回 null
112+
if (p_start == p_end) {
113+
return null;
114+
}
115+
int root_val = preorder[p_start];
116+
TreeNode root = new TreeNode(root_val);
117+
// 在中序遍历中找到根节点的位置
118+
int i_root_index = 0;
119+
for (int i = i_start; i < i_end; i++) {
120+
if (root_val == inorder[i]) {
121+
i_root_index = i;
122+
break;
123+
}
124+
}
125+
int leftNum = i_root_index - i_start;
126+
// 递归的构造左子树
127+
root.left = buildTreeHelper(preorder, p_start + 1, p_start + leftNum + 1, inorder, i_start, i_root_index);
128+
// 递归的构造右子树
129+
root.right = buildTreeHelper(preorder, p_start + leftNum + 1, p_end, inorder, i_root_index + 1, i_end);
130+
return root;
131+
}
132+
```
133+
134+
但是竟然遇到了 `WA`
135+
136+
![](https://windliang.oss-cn-beijing.aliyuncs.com/297_2.jpg)
137+
138+
我开始看到的结果的时候真的小小的震惊了一下,哪里出了问题。用的都是之前的代码,只可能是字符串转换那里出问题了。然后调试了一下发现没有问题,甚至又回到之前的题重新提交了一下,也是没有问题的。
139+
140+
先序遍历和中序遍历唯一确定一个二叉树,这个定理错了???然后用上边的样例调试了一下,恍然大悟,这个定理的前提必须得是没有重复的元素。
141+
142+
# 解法二
143+
144+
好吧,看来不能偷懒。那我们就用 `leetcode` 所使用的方式吧,通过层次遍历来序列化和还原二叉树。
145+
146+
我们只需要将每一层的序列存到数组中,如果是 `null` 就存 `null`。可以结合 [102 题](https://leetcode.wang/leetcode-102-Binary-Tree-Level-Order-Traversal.html) 二叉树的层次遍历。
147+
148+
```java
149+
// Encodes a tree to a single string.
150+
public String serialize(TreeNode root) {
151+
if (root == null) {
152+
return "";
153+
}
154+
Queue<TreeNode> queue = new LinkedList<TreeNode>();
155+
List<Integer> res = new LinkedList<Integer>();
156+
queue.offer(root);
157+
//BFS
158+
while (!queue.isEmpty()) {
159+
TreeNode curNode = queue.poll();
160+
if (curNode != null) {
161+
res.add(curNode.val);
162+
queue.offer(curNode.left);
163+
queue.offer(curNode.right);
164+
} else {
165+
res.add(null);
166+
}
167+
}
168+
return res.toString();
169+
}
170+
171+
// Decodes your encoded data to tree.
172+
public TreeNode deserialize(String data) {
173+
if (data.length() == 0) {
174+
return null;
175+
}
176+
//将字符串还原为数组
177+
String[] preStr = data.substring(1, data.length() - 1).split(",");
178+
Integer[] bfsOrder = new Integer[preStr.length];
179+
for (int i = 0; i < preStr.length; i++) {
180+
if (preStr[i].trim().equals("null")) {
181+
bfsOrder[i] = null;
182+
} else {
183+
bfsOrder[i] = Integer.parseInt(preStr[i].trim());
184+
}
185+
}
186+
187+
Queue<TreeNode> queue = new LinkedList<TreeNode>();
188+
TreeNode root = new TreeNode(bfsOrder[0]);
189+
int cur = 1;//通过 cur 指针依次给节点赋值
190+
queue.offer(root);
191+
while (!queue.isEmpty()) {
192+
TreeNode curNode = queue.poll();
193+
if (bfsOrder[cur] != null) {
194+
curNode.left = new TreeNode(bfsOrder[cur]);
195+
queue.add(curNode.left);
196+
}
197+
cur++;
198+
if (bfsOrder[cur] != null) {
199+
curNode.right = new TreeNode(bfsOrder[cur]);
200+
queue.add(curNode.right);
201+
}
202+
cur++;
203+
}
204+
return root;
205+
}
206+
```
207+
208+
上边的方法已经可以 `AC` 了,但还可以做一个小小的优化。
209+
210+
如果通过上边的代码,对于下边的二叉树。
211+
212+
```java
213+
1
214+
/ \
215+
2 3
216+
/
217+
4
218+
```
219+
220+
序列化成字符串就是 `"[1, 2, 3, 4, null, null, null, null, null]"`。就是下边的样子。
221+
222+
```java
223+
n 表示 null
224+
1
225+
/ \
226+
2 3
227+
/ \ / \
228+
4 n n n
229+
/ \
230+
n n
231+
```
232+
233+
当我们一层一层的还原的时候,因为 `TreeNode` 的默认值就是 `null`。所以还原到 `4` 的时候后边其实就不需要管了。
234+
235+
因为末尾的 `null` 是没有必要的,所以在返回之前,我们可以把末尾的 `null` 去掉。此外,`deserialize()` 函数中,因为我们去掉了末尾的 `null`,所以当 `cur` 到达数组末尾的时候要提前结束循环。
236+
237+
```java
238+
// Encodes a tree to a single string.
239+
public String serialize(TreeNode root) {
240+
if (root == null) {
241+
return "";
242+
}
243+
Queue<TreeNode> queue = new LinkedList<TreeNode>();
244+
List<Integer> res = new LinkedList<Integer>();
245+
queue.offer(root);
246+
while (!queue.isEmpty()) {
247+
TreeNode curNode = queue.poll();
248+
if (curNode != null) {
249+
res.add(curNode.val);
250+
queue.offer(curNode.left);
251+
queue.offer(curNode.right);
252+
} else {
253+
res.add(null);
254+
}
255+
}
256+
//去掉末尾的 null
257+
while (true) {
258+
if (res.get(res.size() - 1) == null) {
259+
res.remove(res.size() - 1);
260+
} else {
261+
break;
262+
}
263+
}
264+
return res.toString();
265+
}
266+
267+
// Decodes your encoded data to tree.
268+
public TreeNode deserialize(String data) {
269+
if (data.length() == 0) {
270+
return null;
271+
}
272+
String[] preStr = data.substring(1, data.length() - 1).split(",");
273+
Integer[] bfsOrder = new Integer[preStr.length];
274+
for (int i = 0; i < preStr.length; i++) {
275+
if (preStr[i].trim().equals("null")) {
276+
bfsOrder[i] = null;
277+
} else {
278+
bfsOrder[i] = Integer.parseInt(preStr[i].trim());
279+
}
280+
}
281+
282+
Queue<TreeNode> queue = new LinkedList<TreeNode>();
283+
TreeNode root = new TreeNode(bfsOrder[0]);
284+
int cur = 1;
285+
queue.offer(root);
286+
while (!queue.isEmpty()) {
287+
if (cur == bfsOrder.length) {
288+
break;
289+
}
290+
TreeNode curNode = queue.poll();
291+
if (bfsOrder[cur] != null) {
292+
curNode.left = new TreeNode(bfsOrder[cur]);
293+
queue.add(curNode.left);
294+
}
295+
cur++;
296+
if (cur == bfsOrder.length) {
297+
break;
298+
}
299+
if (bfsOrder[cur] != null) {
300+
curNode.right = new TreeNode(bfsOrder[cur]);
301+
queue.add(curNode.right);
302+
}
303+
cur++;
304+
}
305+
return root;
306+
}
307+
```
308+
309+
# 解法三
310+
311+
我们可以只用先序遍历。什么???只用先序遍历,是的,你没有听错。我开始也没往这方面想。直到看到 [这里](https://leetcode.com/problems/serialize-and-deserialize-binary-tree/discuss/74253/Easy-to-understand-Java-Solution) 的题解。
312+
313+
为什么可以只用先序遍历?因为我们先序遍历过程中把遇到的 `null` 也保存起来了。所以本质上和解法二的 `BFS` 是一样的。
314+
315+
此外,他没有套用之前先序遍历的代码,重写了先序遍历,在遍历过程中生成序列化的字符串。
316+
317+
```java
318+
private static final String spliter = ",";
319+
private static final String NN = "X"; //当做 null
320+
321+
// Encodes a tree to a single string.
322+
public String serialize(TreeNode root) {
323+
StringBuilder sb = new StringBuilder();
324+
buildString(root, sb);
325+
return sb.toString();
326+
}
327+
328+
private void buildString(TreeNode node, StringBuilder sb) {
329+
if (node == null) {
330+
sb.append(NN).append(spliter);
331+
} else {
332+
sb.append(node.val).append(spliter);
333+
buildString(node.left, sb);
334+
buildString(node.right,sb);
335+
}
336+
}
337+
// Decodes your encoded data to tree.
338+
public TreeNode deserialize(String data) {
339+
Deque<String> nodes = new LinkedList<>();
340+
nodes.addAll(Arrays.asList(data.split(spliter)));
341+
return buildTree(nodes);
342+
}
343+
344+
private TreeNode buildTree(Deque<String> nodes) {
345+
String val = nodes.remove();
346+
if (val.equals(NN)) return null;
347+
else {
348+
TreeNode node = new TreeNode(Integer.valueOf(val));
349+
node.left = buildTree(nodes);
350+
node.right = buildTree(nodes);
351+
return node;
352+
}
353+
}
354+
```
355+
356+
#
357+
358+
这道题的话完善了自己脑子里的一些认识,先序遍历和中序遍历可以唯一的确定一个二叉树,前提是元素必须不一样。其实看通过先序遍历和中序遍历还原二叉树的代码也可以知道,因为我们需要找根节点的下标,如果有重复的值,肯定就不行了。
359+
360+
其次,如果二叉树的遍历考虑了 `null`,那么不管什么遍历我们都能把二叉树还原。

0 commit comments

Comments
 (0)