Serialize and Deserialize Binary Tree
Design a class Codec with two methods:
serialize(root)takes the root of a binary tree and returns a string that encodes it.deserialize(data)takes a string produced byserializeand rebuilds the tree.
The string format is entirely your choice. The only requirement is that deserialize(serialize(root)) produces a tree with exactly the same shape and the same value at every node. The tree may be empty, and values may repeat.
The judge passes in a tree written as a level-order array with null for missing children, for example [8, 3, 10, null, 6], and checks the tree you rebuild against it. It may use separate Codec instances for the two calls, so keep no state between them.
Example 1
Example 2
Example 3
Constraints
The tree has between 0 and 104 nodes.-1000 <= Node.val <= 1000
Pattern Path · Trees: BFS · problem 97 of 169
#. With those markers, one traversal fully describes the tree.Start simple
Store both the pre-order and in-order value sequences and rebuild the tree from the pair: O(n) with a hash map, but it only works when all values are distinct, which this problem does not promise.
The key idea
A single traversal order (pre-order, level order, ...) is not enough by itself, because different shapes can give the same value sequence. The fix is to also write down the missing children. With a marker such as # for every empty slot, the sequence pins down the shape exactly.
serialize: walk the tree level by level with a queue. For a real node, output its value and enqueue both children, even empty ones. For an empty slot, output #. Join the tokens with commas.
deserialize: split the string into tokens. The first token is the root, or # for an empty tree. Then repeat the walk: pop a node from the queue, and the next two tokens are its left and right child. Each non-# token becomes a new node that joins the queue. This works because serialize emitted exactly two child tokens for every real node, in the same queue order.
An iterative queue also avoids recursion-depth limits on very deep, skewed trees. A recursive pre-order version with null markers is an equally good answer when recursion depth is not a concern.
Complexity
- Time:
O(n) - Space:
O(n)
Python
class Codec:
def serialize(self, root: Optional[TreeNode]) -> str:
"""Level-order walk; '#' marks every missing child."""
tokens = []
queue = deque([root])
while queue:
node = queue.popleft()
if node is None:
tokens.append("#")
else:
tokens.append(str(node.val))
queue.append(node.left)
queue.append(node.right)
return ",".join(tokens)
def deserialize(self, data: str) -> Optional[TreeNode]:
tokens = data.split(",")
if tokens[0] == "#":
return None
root = TreeNode(int(tokens[0]))
queue = deque([root])
i = 1
while queue:
node = queue.popleft()
# The next two tokens are this node's left and right child.
if tokens[i] != "#":
node.left = TreeNode(int(tokens[i]))
queue.append(node.left)
if tokens[i + 1] != "#":
node.right = TreeNode(int(tokens[i + 1]))
queue.append(node.right)
i += 2
return root
JavaScript
class Codec {
// Level-order walk; '#' marks every missing child.
serialize(root) {
const tokens = [];
const queue = [root];
for (let head = 0; head < queue.length; head++) {
const node = queue[head];
if (node === null) {
tokens.push("#");
} else {
tokens.push(String(node.val));
queue.push(node.left, node.right);
}
}
return tokens.join(",");
}
deserialize(data) {
const tokens = data.split(",");
if (tokens[0] === "#") return null;
const root = new TreeNode(Number(tokens[0]));
const queue = [root];
let i = 1;
for (let head = 0; head < queue.length; head++) {
const node = queue[head];
// The next two tokens are this node's left and right child.
if (tokens[i] !== "#") {
node.left = new TreeNode(Number(tokens[i]));
queue.push(node.left);
}
if (tokens[i + 1] !== "#") {
node.right = new TreeNode(Number(tokens[i + 1]));
queue.push(node.right);
}
i += 2;
}
return root;
}
}
What the interviewer is checking
The interviewer wants to see you notice why a plain traversal is ambiguous, pick a format that encodes the shape, and write a decoder that mirrors the encoder exactly, including the empty tree.
Mistakes to avoid
- Leaving out null markers, so different tree shapes give the same string.
- Parsing values character by character, which breaks on multi-digit and negative numbers; split on a delimiter instead.
- Forgetting the empty tree, or crashing when the input string encodes only
#. - Recursing on a very deep, skewed tree and hitting the recursion limit.
Follow-ups to expect
- How would you make the string as short as possible? (Trim trailing markers, binary encoding.)
- What changes for a binary search tree? (Pre-order alone suffices; no markers.)
- How would you serialize an N-ary tree? (Store each node's child count.)
In real software
Saving a parsed syntax tree, caching a decision tree model to disk, or sending a DOM-like structure over the network all require turning a pointer-based tree into a flat string and back.