Problem Walkthrough

Serialize Binary Tree LeetCode 297 Guide

LeetCode 297 Serialize and Deserialize Binary Tree asks you to design a codec that can convert any binary tree to a string and reconstruct the original tree from that string. The cleanest interview approach uses preorder DFS with null markers — each node emits its value, then recurses left and right, using a sentinel like "null" for missing children. Deserialization consumes an iterator over the comma-split string, recursively building left and right subtrees in the same order.

9 min read|

Serialize Binary Tree

LeetCode 297

Problem Overview: Design a Tree Codec

LeetCode 297 Serialize and Deserialize Binary Tree asks you to design a Codec class with two methods: serialize(root) converts a binary tree to a string, and deserialize(data) converts that string back to the original binary tree. The key constraint is that the round trip must be lossless — deserializing the serialized output must produce a tree that is structurally and value-identical to the original.

This is an open-ended design problem: the format of the string is entirely up to you. There is no single correct encoding, but some encodings are much easier to implement than others. The challenge is choosing a format that uniquely identifies the tree structure — not just the values — so that deserialization can reconstruct the exact shape, including all null children.

The problem applies to any binary tree: balanced or skewed, complete or sparse, with duplicate values. You cannot assume any special structure that would simplify encoding. A robust solution must handle trees with a single node, completely left-skewed or right-skewed trees, and trees where many internal nodes have only one child.

  • Input to serialize: TreeNode root (may be null)
  • Output of serialize: string in any format of your choice
  • Input to deserialize: string produced by your serialize method
  • Output of deserialize: TreeNode root reconstructing the original tree
  • Must handle any binary tree including nulls, skewed trees, and duplicates
  • Node values are integers; tree can have up to 10^4 nodes

Why Inorder Alone Is Not Enough

Inorder traversal visits left subtree, then the current node, then right subtree. For a binary search tree, inorder produces a sorted sequence that uniquely defines the values — but not the shape. Many structurally different trees produce the same inorder sequence. For example, a root with value 2, left child 1, and right child 3 produces the same inorder as a root with value 1 and right child 2 that has a right child 3. Without knowing the root or the shape, you cannot reconstruct the tree from inorder alone.

Combining two traversals — such as preorder + inorder or postorder + inorder — does uniquely define a binary tree when all values are distinct. But this approach requires two passes and non-trivial reconstruction logic that is error-prone in an interview setting. It also fails when the tree contains duplicate values, making it unsuitable as a general-purpose codec.

The solution is to include structural information directly in the serialization. By recording null children as explicit markers, a single traversal — preorder or level-order — uniquely defines both the values and the shape. You no longer need a second traversal because the null markers tell you exactly where each subtree ends.

💡

Preorder with Null Markers Is the Simplest Interview Approach

Adding null markers to a preorder traversal gives you a format that uniquely encodes the tree structure in a single pass. The resulting string can be split by comma and deserialized with a simple recursive iterator — no need to find subtree boundaries or handle two separate traversal arrays. This is the most concise correct solution for LeetCode 297 and the approach most interviewers expect.

Preorder Serialization: DFS with Null Markers

Preorder DFS visits a node before its children: first the node itself, then the left subtree, then the right subtree. To serialize a binary tree using preorder, you apply this traversal recursively. When you reach a non-null node, you emit its value. When you reach a null child, you emit a sentinel — typically the string "null" or the character "#". All emitted values are collected into a list and joined with commas to produce the final string.

The result is a flat, linear string that encodes both values and structure. For a tree with root 1, left child 2, and right child 3, the preorder with null markers produces "1,2,null,null,3,null,null". Every leaf is followed by two null markers for its absent left and right children. Every internal node with only one child has one null marker for the missing child. This encoding is unambiguous: there is exactly one tree that produces any given valid preorder string with null markers.

The serialization is a single DFS pass — O(n) time and O(n) space for the output list plus the recursion stack. The recursion depth is O(h) where h is the tree height, which is O(n) in the worst case for a skewed tree. The comma-separated format makes splitting trivial during deserialization.

  1. 1Define serialize(root): call helper(root), join result list with commas
  2. 2helper(node): if node is None, append "null" to result and return
  3. 3Otherwise append str(node.val) to result
  4. 4Recurse: helper(node.left), then helper(node.right)
  5. 5Final string: ",".join(result_list)

Preorder Deserialization: Iterator-Based Reconstruction

To deserialize, split the string by comma to get a list of tokens. The key insight is that the tokens are in preorder, so you can reconstruct the tree by consuming tokens in order — the first token is the root, the next tokens recursively define the left subtree, and then the right subtree. The null markers tell you exactly when a subtree is empty and you should stop recursing.

The cleanest implementation uses an iterator or a deque over the token list. Each recursive call consumes exactly one token from the front of the iterator. If the token is "null", return None immediately (this subtree is empty). Otherwise, create a new TreeNode with the integer value of the token, then recursively assign its left child (consuming the next token) and its right child (consuming the token after that). Return the constructed node.

This iterator approach eliminates the need to pass an index by reference or return a (node, index) tuple. The iterator's internal pointer advances automatically with each next() call, making the recursive code as clean as the serialization code. The deserialization is also O(n) time and O(n) space for the recursion stack plus the token list.

ℹ️

The Iterator Approach Avoids Index Passing and Makes Code Much Cleaner

A common mistake in deserialization is trying to pass an index variable into the recursive function. In Python, integers are immutable, so you cannot update an index in a nested call without using a list wrapper or nonlocal. Using iter() and next() sidesteps this entirely: the iterator object is mutable and shared across all recursive calls. In Java, wrap the token array in a Queue or use an int[] index wrapper for the same effect.

BFS Level-Order Alternative

An alternative to preorder DFS is BFS level-order serialization. You use a queue starting with the root. For each node dequeued, you emit its value (or "null" if it is None). For non-null nodes, you enqueue both children — even null ones — so that their positions are recorded in the output. This produces a level-by-level encoding similar to LeetCode's own tree representation format.

Level-order serialization naturally mirrors the way LeetCode displays trees in problem statements, which can make debugging easier. The tradeoff is that it tends to produce longer strings than preorder for sparse trees, because you must emit nulls for all missing children at every level until the last non-null node in the final level.

Deserialization from a BFS encoding also uses a queue. Read the first token as the root. Then use a queue of parent nodes. For each parent dequeued, read the next two tokens as its left and right children. If a token is "null", that child is null (and is not enqueued). If it is a value, create the child node and enqueue it for its own children to be assigned. Continue until all tokens are consumed.

  1. 1Serialize: queue = deque([root]); result = []
  2. 2While queue: node = queue.popleft(); if node is None, append "null"; else append str(node.val), enqueue node.left and node.right
  3. 3Join result with commas
  4. 4Deserialize: tokens = data.split(","); root = TreeNode(int(tokens[0]))
  5. 5queue = deque([root]); i = 1
  6. 6While queue: node = queue.popleft(); assign node.left from tokens[i], node.right from tokens[i+1]; enqueue non-null children; i += 2

Code Walkthrough — Python and Java

Python preorder Codec: class Codec: def serialize(self, root): res=[]; def dfs(n): res.append(str(n.val) if n else "null"); n and (dfs(n.left) or dfs(n.right)); dfs(root); return ",".join(res). def deserialize(self, data): it=iter(data.split(",")); def dfs(): v=next(it); return None if v=="null" else TreeNode(int(v),dfs(),dfs()); return dfs(). The serialize and deserialize methods are each under 5 lines of meaningful code. Time and space are O(n) in both directions.

Java preorder Codec: public String serialize(TreeNode root){ StringBuilder sb=new StringBuilder(); dfs(root,sb); return sb.toString(); } void dfs(TreeNode n,StringBuilder sb){ if(n==null){sb.append("null,");return;} sb.append(n.val+","); dfs(n.left,sb); dfs(n.right,sb); } public TreeNode deserialize(String data){ Queue<String> q=new LinkedList<>(Arrays.asList(data.split(","))); return dfs(q); } TreeNode dfs(Queue<String> q){ String s=q.poll(); if(s.equals("null"))return null; TreeNode n=new TreeNode(Integer.parseInt(s)); n.left=dfs(q); n.right=dfs(q); return n; }

Both implementations follow the same structure. The preorder approach is shorter and more elegant than the BFS approach, especially in Python. The O(n) time and space guarantees hold for both the serialize and deserialize directions. The preorder codec also handles skewed trees naturally, while BFS may allocate a very wide queue for wide trees.

⚠️

Handle the Edge Case of an Empty Tree

If the root is null, serialize should return an empty string or a single "null" token — not an empty list that causes an index error on split. In the preorder DFS approach, calling dfs(None) appends "null" to the result, so serialize(null) returns "null". In deserialize, the first next(it) call returns "null" and the function immediately returns None, correctly reconstructing the empty tree. Make sure your implementation handles this case before submitting.

Ready to master algorithm patterns?

YeetCode flashcards help you build pattern recognition through active recall and spaced repetition.

Start practicing now