Serialize and Deserialize Binary Tree hard
Problem Statement
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.
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.
You may serialize the following tree:
1
/ \
2 3
/ \
4 5
as "[1,2,3,null,null,4,5]"
Example 1
Input: root = [1,2,3,null,null,4,5] Output: [1,2,3,null,null,4,5]
Example 2
Input: root = [] Output: []
Steps
-
Serialization: We perform a preorder traversal of the binary tree. For each node, we append its value to the string. If a node is null, we append "null". We separate values with commas.
-
Deserialization: We reconstruct the tree from the serialized string. We use a queue to track nodes we need to process. We read values from the string one by one. If the value is not "null", we create a new node and add it to the queue. Then, we assign left and right children to the current node from the queue, handling null values appropriately.
Explanation
Serialization: The preorder traversal guarantees that we process the root node first, followed by its left subtree and then its right subtree. This ordering is crucial for the correct reconstruction during deserialization. Using "null" as a placeholder ensures that we correctly represent missing nodes in the tree structure.
Deserialization: The deserialization process reverses the serialization process. The queue helps maintain the order of nodes, ensuring correct connections between parent and child nodes. Reading values from the string and handling "null" values correctly allows for accurate reconstruction of the original tree.
Code
using System;
using System.Collections.Generic;
public class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int x) { val = x; }
}
public class Codec {
// Encodes a tree to a single string.
public string serialize(TreeNode root) {
if (root == null) return "[]";
Queue<TreeNode> q = new Queue<TreeNode>();
q.Enqueue(root);
List<string> vals = new List<string>();
while (q.Count > 0) {
TreeNode curr = q.Dequeue();
if (curr == null) {
vals.Add("null");
} else {
vals.Add(curr.val.ToString());
q.Enqueue(curr.left);
q.Enqueue(curr.right);
}
}
while (vals[vals.Count - 1] == "null" && vals.Count > 1) {
vals.RemoveAt(vals.Count - 1);
}
return "[" + string.Join(",", vals) + "]";
}
// Decodes your encoded data to tree.
public TreeNode deserialize(string data) {
if (data == "[]") return null;
string[] vals = data.Substring(1, data.Length - 2).Split(',');
Queue<TreeNode> q = new Queue<TreeNode>();
TreeNode root = new TreeNode(int.Parse(vals[0]));
q.Enqueue(root);
int i = 1;
while (q.Count > 0) {
TreeNode curr = q.Dequeue();
if (i < vals.Length && vals[i] != "null") {
curr.left = new TreeNode(int.Parse(vals[i]));
q.Enqueue(curr.left);
}
i++;
if (i < vals.Length && vals[i] != "null") {
curr.right = new TreeNode(int.Parse(vals[i]));
q.Enqueue(curr.right);
}
i++;
}
return root;
}
}
Complexity
Serialization:
- Time Complexity: O(N), where N is the number of nodes in the tree. We visit each node exactly once.
- Space Complexity: O(N) in the worst case (a skewed tree), to store the queue and the list of values.
Deserialization:
- Time Complexity: O(N). We process each value in the string once.
- Space Complexity: O(N) in the worst case (a skewed tree), to store the queue.
This improved solution addresses the edge case of trailing nulls in the serialized string and provides a more robust and efficient serialization and deserialization process. The use of queues and lists makes the code cleaner and easier to understand. The error handling is improved for robustness.