Loading problems...
TL;DR: The optimal solution utilizes a Pre-order Depth-First Search (DFS) to record node values and explicitly mark null pointers, allowing the tree structure to be uniquely reconstructed from a linear string.
The "Serialize and Deserialize Binary Tree" problem asks us to design an algorithm that converts a hierarchical binary tree structure into a linear string format (serialization) and subsequently reconstructs that string back into the original binary tree (deserialization). This is a fundamental system design concept used for storing complex data in files or transmitting it over networks.
While LeetCode 297 allows any format, the challenge lies in handling the tree's structure efficiently, particularly ensuring that the parent-child relationships are preserved exactly, even when the tree contains duplicate values or is unbalanced.
A common naive approach to serialization is to attempt to store the tree using a standard traversal (like In-order or Pre-order) without recording null pointers, or by attempting to map the tree to a fixed-size array structure similar to a heap.
For the heap-like array approach (where index i has children at 2*i and 2*i+1):
Why this fails: While this works for complete binary trees, it fails efficiently for skewed trees. If a tree has a depth of but is a straight line (linked list structure), the heap-indexing strategy requires an array of size . For a tree with maximum depth (e.g., 1000), this results in a Memory Limit Exceeded or Time Limit Exceeded error due to the exponential space requirement relative to the number of nodes.
The fundamental challenge in reconstructing a binary tree from a traversal sequence is ambiguity. For example, the sequence [1, 2] could represent a root 1 with a left child 2, or a root 1 with a right child 2.
To resolve this, the Core Insight is to explicitly serialize null pointers.
By including a unique marker (e.g., "N" or "#") for every null child encountered during the traversal, we make the traversal sequence unique to that specific tree structure.
We choose Pre-order DFS (Root -> Left -> Right) for this task because:
Visual Description:
Imagine the recursion tree as a path. The algorithm visits the Root (1). It records "1". It moves Left to (2). It records "2". It moves Left again and hits null. It records "N". It moves Right (of 2) and hits null. It records "N". The recursion unwinds to (1) and moves Right. This deterministic path ensures that the linear string 1,2,N,N,... maps 1-to-1 with the tree topology.

Consider the tree: root = [1, 2, 3, null, null, 4, 5]
Serialization Flow:
1. String: "1,"2. String: "1,2,"null. String: "1,2,N,"null. String: "1,2,N,N," (Node 2 is done)3. String: "1,2,N,N,3,"4. String: "1,2,N,N,3,4,"null. String: "1,2,N,N,3,4,N,"null. String: "1,2,N,N,3,4,N,N," (Node 4 is done)5. String: "1,2,N,N,3,4,N,N,5,"Deserialization Flow:
Tokens: ["1", "2", "N", "N", "3", ...]
"1". Create Node(1).1.left. Pop "2". Create Node(2).2.left. Pop "N". Return null. 2.left is null.2.right. Pop "N". Return null. 2.right is null.1.right. Pop "3". Create Node(3).The correctness relies on the structural properties of Pre-order traversal extended with null markers.
cpp/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Codec { public: // Encodes a tree to a single string. string serialize(TreeNode* root) { if (!root) return "N"; // Pre-order: Root, Left, Right return to_string(root->val) + "," + serialize(root->left) + "," + serialize(root->right); } // Decodes your encoded data to tree. TreeNode* deserialize(string data) { stringstream ss(data); string item; queue<string> q; // Split string by comma while (getline(ss, item, ',')) { q.push(item); } return deserializeHelper(q); } private: TreeNode* deserializeHelper(queue<string>& q) { string val = q.front(); q.pop(); if (val == "N") { return nullptr; } TreeNode* node = new TreeNode(stoi(val)); node->left = deserializeHelper(q); node->right = deserializeHelper(q); return node; } };
java/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ public class Codec { private static final String SPLITTER = ","; private static final String NN = "N"; // Encodes a tree to a single string. public String serialize(TreeNode root) { StringBuilder sb = new StringBuilder(); buildString(root, sb); return sb.toString(); } private void buildString(TreeNode node, StringBuilder sb) { if (node == null) { sb.append(NN).append(SPLITTER); } else { sb.append(node.val).append(SPLITTER); buildString(node.left, sb); buildString(node.right, sb); } } // Decodes your encoded data to tree. public TreeNode deserialize(String data) { Deque<String> nodes = new LinkedList<>(); nodes.addAll(Arrays.asList(data.split(SPLITTER))); return buildTree(nodes); } private TreeNode buildTree(Deque<String> nodes) { String val = nodes.remove(); if (val.equals(NN)) { return null; } else { TreeNode node = new TreeNode(Integer.valueOf(val)); node.left = buildTree(nodes); node.right = buildTree(nodes); return node; } } }
python# Definition for a binary tree node. # class TreeNode(object): # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Codec: def serialize(self, root): """Encodes a tree to a single string. :type root: TreeNode :rtype: str """ vals = [] def dfs(node): if not node: vals.append("N") return vals.append(str(node.val)) dfs(node.left) dfs(node.right) dfs(root) return ",".join(vals) def deserialize(self, data): """Decodes your encoded data to tree. :type data: str :rtype: TreeNode """ vals = iter(data.split(",")) def build(): try: val = next(vals) except StopIteration: return None if val == "N": return None node = TreeNode(int(val)) node.left = build() node.right = build() return node return build()
Time Complexity:
Space Complexity:
The Tree - Serialization and Deserialization subpattern is a versatile tool in interview questions.
Mistake: Using string concatenation (s = s + val) inside a recursive loop instead of a StringBuilder or list join.
Why: In many languages (like Java and Python), strings are immutable. Concatenation creates a new string copy every time, leading to time and space complexity.
Consequence: The solution fails on large inputs due to inefficient memory usage.
Mistake: Attempting to reconstruct the tree using only In-order traversal.
Why: In-order traversal (Left, Root, Right) creates ambiguity regarding the root node. [2, 1, 3] could be 1 as root with children 2 and 3, or a skewed tree.
Consequence: You cannot uniquely identify the tree structure without a second traversal type (like Pre-order) or explicit null markers in a Pre-order/Level-order traversal.
Mistake: Using list.pop(0) in Python or equivalent shift operations in a loop.
Why: Removing the first element of a standard array/list shifts all subsequent elements, making the deserialization .
Consequence: Time Limit Exceeded (TLE). Always use a Queue, Deque, or an iterator to consume tokens in .