Serialize and Deserialize Binary Tree asks you to turn a tree into a string and back again with no loss. It is a favorite interview question because the trick — record the nulls — is small but easy to miss, and it tests whether you really understand a preorder traversal.
Problem. Design two functions: serialize(root) turns a binary tree into a single string, and
deserialize(data) turns that string back into the exact same tree. Node values fit in an int.
Example: the tree with root 1, left child 2, right child 3, and 3 having children 4 and 5
serializes to "1 2 N N 3 4 N N 5 N N" and deserializes back to the identical tree.
The slow way first
A first instinct is to store just the values in some order, like a plain inorder list 2 1 4 3 5. But that fails: many different trees produce the same list of values, so you cannot tell which tree to rebuild. The structure — which child is missing — is lost.
The question to ask: what extra information do I need so the string is unambiguous? The answer is the null children. If the string also records every missing child, exactly one tree matches it.
The idea: preorder, and write "N" for null
Do a preorder walk (node, then left, then right). Append each node's value to the output. When the walk reaches a null child, append a sentinel "N" instead of skipping it. Those N markers pin down the shape.
To rebuild, read the tokens left to right with a single cursor. The first token is the root; a number means make a node and then build its left and right from the following tokens; an "N" means that child is null. Because we read in the same preorder we wrote, the cursor always lands on the right token.
The key insight: a preorder list with the nulls written in has exactly one matching tree, so the round trip is lossless.
Walk through it
Step through the animation. First serialize fills the token strip: each node writes its value, each null writes an "N". Then deserialize runs the cursor back across those same tokens, building one node per number and stopping a branch on each "N". The rebuilt tree on the left ends up identical to the one we started with.
Pseudocode
serialize(root):
out = empty list
walk(node):
if node is null:
append "N" to out # record the missing child
return
append node.value to out
walk(node.left) # preorder: left, then right
walk(node.right)
walk(root)
return out joined by spaces
deserialize(data):
tokens = a left-to-right cursor over the split string
build():
tok = next token
if tok == "N": return null
node = new node with value tok
node.left = build() # same preorder order
node.right = build()
return node
return build()The Python solution
def serialize(root):
out = []
def walk(node):
if node is None:
out.append("N")
return
out.append(str(node.val))
walk(node.left)
walk(node.right)
walk(root)
return " ".join(out)
def deserialize(data):
tokens = iter(data.split())
def build():
tok = next(tokens)
if tok == "N":
return None
node = TreeNode(int(tok))
node.left = build()
node.right = build()
return node
return build()walkis the preorder traversal. A null node appends"N"and returns — that sentinel is the whole trick.- A real node appends its value, then recurses left then right, in that fixed order.
" ".join(out)flattens the list into one space-separated string.iter(data.split())makes a one-way cursor;next(tokens)hands back the next token each call.buildmirrorswalkexactly: read a token, return null on"N", else make a node and fill left then right. Reading in the same preorder is what keeps the cursor aligned.
Complexity
| Case | Time | Notes |
|---|---|---|
| serialize | O(n) (moderate) | visit each node and each null once |
| deserialize | O(n) (moderate) | consume each token once |
O(n) (moderate)Both directions touch every node (and every null marker) a constant number of times, so both are linear. The string and the recursion stack are each O(n).
When this pattern shows up
Whenever you must encode a tree (or any recursive structure) as a flat string, the move is a traversal
plus a null marker. Preorder is the easiest to rebuild because the first token is always the next
node to create. A shared cursor (iter / an index) keeps serialize and deserialize in lockstep.
Forgetting the null markers is the classic bug: without them the string is ambiguous and cannot be rebuilt. Also keep serialize and deserialize in the same traversal order — if one is preorder and the other expects a different order, the cursor reads the wrong tokens.
Practice
During serialize, when the walk reaches node 2 (a leaf), what gets appended to the output for it and its children?
1. Why do we write 'N' for null children instead of skipping them?
2. Why does deserialize use the same preorder order as serialize?
3. What does the first token of the string always represent?
4. What is the time complexity of deserialize?