Serialize and Deserialize Binary Tree
Design two functions. serialize(root) turns a binary tree into a single string; deserialize(data) turns such a string back into a tree that is structurally identical to the original, with the same values in the same positions. The string format is entirely your choice — you are only required to be able to read back what you wrote. An empty tree must survive the round trip too. This is an ordinary binary tree: no ordering of values may be assumed.
- The number of nodes in the tree is in the range [0, 10⁴]
- -1000 <= Node.val <= 1000
- Values may repeat, and may be negative
- You choose the encoding; only the round trip is graded
root = [1,2,3,null,null,4,5]round trip reproduces [1,2,3,null,null,4,5]root = []round trip reproduces []root = [1,2,null,3]round trip reproduces [1,2,null,3]root = [7,7,7]round trip reproduces [7,7,7]A tree is a 2-D shape; a string is a single line. Serialize flattens the tree into a string; deserialize reads it back into the same tree. The only real question: what does "lose nothing" take?
Hand over the pre-order list [1, 2]: 1 is the root, 2 its only child — but which side?
1 1
/ or \
2 2
both give pre-order [1, 2]The information that vanished is the missing child. A traversal lists only the nodes that exist and says nothing about the empty sides — so the reader can't tell the two shapes apart.
Emit a marker — say # — for every null link. Now the two shapes become different strings:
1 -> 1,2,#,#,# (2 on the left; 2's two nulls; then 1's right null)
/
2
1 -> 1,#,2,#,# (1's left null; then 2 with its two nulls)
\
2With nulls included, a single pre-order string rebuilds the tree with zero guessing. Every token is a complete instruction: a value means "make a node — the next tokens are its whole left subtree, then its whole right subtree"; a # means "empty here, consume nothing more." So the reader just scans left to right, no lookahead.
def serialize(node):
if node is None:
return "#" # the gap is data too
return f"{node.val},{serialize(node.left)},{serialize(node.right)}"
def deserialize(data):
tokens = iter(data.split(",")) # read strictly left to right
def build():
tok = next(tokens) # consume exactly one token
if tok == "#":
return None # empty subtree — nothing more to read
node = Node(int(tok))
node.left = build() # next tokens = the whole left subtree
node.right = build() # then the whole right subtree
return node
return build()Notice the mirror: the writer recurses node, left, right; the reader recurses node, left, right. Same shape — the reader is just replaying the writer's walk.
build must consume its token before recursing, and assign left then right. The stream carries no positions — only order — so correctness rests entirely on reading tokens in the exact order they were written. Swap the two lines and you get a valid-looking but silently mirrored tree.Tree: root 1, children 2 and 3; 3's children 4 and 5.
serialize (pre-order + nulls):
1
/ \ -> 1, 2,#,#, 3, 4,#,#, 5,#,#
2 3
/ \
4 5
deserialize, token by token:
1 -> node 1, build its left...
2 -> node 2; left #=null, right #=null -> leaf, becomes 1.left
3 -> node 3, build its left...
4 -> #, # -> leaf 4 (3.left)
5 -> #, # -> leaf 5 (3.right)
stream empty -> tree completeEleven tokens for five nodes — six of them markers. That's the price, and it's cheap.
Every node written once, every null link written once. A tree with N nodes has N + 1 null links, so the string is 2N + 1 tokens: O(N) time and output; deserialize is O(N) too, O(H) recursion depth. (A level-order/BFS encoding with markers works equally well — that's the familiar LeetCode array format.)
The transferable principle reaches well past trees: you can flatten a structure into a line without loss exactly when the reader can tell where each part ends. Either send a second view that pins the boundaries, or write explicit terminators so each part announces its own end (the # markers here). Length prefixes are a third option — and JSON, protobuf, and network protocols all pick one of the three. Designing any encoding, ask first: what tells the reader where this piece stops?
Object Persistence
Serialization & Deserialization Pipeline