一、题目描述
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
示例 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, 10^4]
内 -1000 <= Node.val <= 1000
二、解题思路
1. 序列化
- 使用前序遍历递归地将每个节点转换为字符串,如果节点为空,则用"null"表示。
- 使用逗号分隔每个节点值,形成完整的序列化字符串。
2. 反序列化
- 使用逗号分割序列化字符串,得到节点值的数组。
- 使用一个队列(或列表)来存储节点值,以便于在构建树时按顺序访问。
- 递归地构建树,每次从队列中取出一个值,如果值为"null",则返回null表示空节点,否则创建一个新的节点,并递归构建其左右子树。
三、具体代码
import java.util.*;
// TreeNode 类现在是一个顶级类,不再嵌套在 Codec 类内部
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) {
if (root == null) return "null,";
// 前序遍历序列化
return root.val + "," + serialize(root.left) + serialize(root.right);
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
Queue<String> nodes = new LinkedList<>(Arrays.asList(data.split(",")));
return deserializeHelper(nodes);
}
private TreeNode deserializeHelper(Queue<String> nodes) {
String val = nodes.poll();
if ("null".equals(val)) return null;
// 创建当前节点,并递归构建左右子树
TreeNode root = new TreeNode(Integer.parseInt(val));
root.left = deserializeHelper(nodes);
root.right = deserializeHelper(nodes);
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));
四、时间复杂度和空间复杂度
1. 时间复杂度
(1)serialize 方法
serialize
方法采用前序遍历的方式来序列化二叉树。- 对于树中的每个节点,我们都会进行一次访问。
- 时间复杂度是 O(n),其中 n 是树中节点的数量。
(2)deserialize 方法
deserialize
方法使用队列来反序列化字符串表示的树。- 在反序列化过程中,我们同样需要访问每个节点一次。
- 时间复杂度也是 O(n),因为每个节点都会被处理一次。
2. 空间复杂度
(1)serialize 方法
serialize
方法递归地序列化树,递归栈的深度取决于树的高度。- 在最坏的情况下(当树退化成一条链时),递归栈的深度为 n。
- 因此,空间复杂度是 O(n),其中 n 是树的高度。
(2)deserialize 方法
deserialize
方法使用了一个队列来存储序列化字符串分割后的字符串数组。- 队列的大小最多是序列化字符串中逗号的数量加一,即 n + 1(每个节点后都有一个逗号,除了最后一个节点)。
- 因此,空间复杂度是 O(n),其中 n 是树中节点的数量。
3. 总结
- 时间复杂度:O(n),其中 n 是树中节点的数量。
- 空间复杂度:O(n),其中 n 是树中节点的数量或树的高度,取决于具体是序列化还是反序列化操作。
这些分析假设了树是通过指针或引用连接的,并且不考虑字符串操作(如连接和分割)的具体实现细节,这些操作在最坏情况下的时间复杂度可能会影响总的时间复杂度。在实际应用中,字符串操作的时间复杂度可能会对性能有显著影响。
五、总结知识点
-
类定义:
class
关键字用于定义一个类。TreeNode
类定义了一个二叉树节点,包含整数值val
和指向左右子节点的引用left
和right
。
-
构造函数:
TreeNode(int x)
是TreeNode
类的构造函数,用于创建树节点并初始化其值。
-
递归:
serialize
和deserializeHelper
方法都使用了递归来处理树结构。递归是一种常见的算法技巧,用于处理树和图等分治问题。
-
字符串操作:
- 字符串连接操作
+
用于将节点值和递归序列化的子树字符串拼接起来。 split
方法用于将字符串按指定分隔符(这里是逗号)分割成字符串数组。
- 字符串连接操作
-
队列的使用:
Queue
接口和LinkedList
类用于存储和访问序列化字符串分割后的节点值。队列是一种先进先出(FIFO)的数据结构,适合用于这种按顺序处理元素的场景。
-
异常处理:
deserializeHelper
方法中,通过比较字符串"null"
来处理空节点的情况,这是一种简单的异常处理机制。
-
类型转换:
Integer.parseInt(val)
用于将字符串转换为整数,这是基本类型转换的一个例子。
-
接口与实现:
Codec
类实现了序列化和反序列化二叉树的功能,它是一个接口的具体实现。
-
实例化对象:
new
关键字用于创建Codec
和TreeNode
类的实例。
-
方法重载:
serialize
和deserialize
方法都是Codec
类的成员方法,它们被设计为处理不同的输入(一个是TreeNode
对象,另一个是字符串)。
-
成员变量与局部变量:
val
,left
,right
是TreeNode
类的成员变量,用于存储树节点的状态。nodes
,val
,root
是deserializeHelper
方法的局部变量,用于在方法执行期间存储临时数据。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。