Approach Summary
Pre-order DFS serialization with null markers. Deserialization reconstructs using a queue of values.
Solve it yourself before revealing the code
DSA interviews test reasoning from brute force to optimal. Think through the constraints first.
Solution Progression (Brute Force → Most Optimized)
Understand the architectural evolution from naive baseline to optimal complexity.
| Approach | Time | Space | Core Strategy / Trade-off |
|---|---|---|---|
| 1 Approach 1: BFS Level-Order with Delimiters (Optimal) | O(n) | O(n) | Encode level-by-level with null markers (#); reconstruct children pairs using a FIFO queue. |
| 2 Approach 2: Breadth-First Search (BFS) Level-Order with Queue | O(n) | O(n) | Serializes using BFS queue inserting "null" tokens, deserializing with parent queue. |
| 3 Approach 2: Breadth-First Search (BFS) Level-Order with Queue | O(n) | O(n) | Serializes using BFS queue inserting "null" tokens, deserializing with parent queue. |
Approach 1: BFS Level-Order with Delimiters (Optimal)
Level-order traversal naturally captures tree structure when null leaf pointers are recorded as '#'. Upon decoding, reading tokens in sequential pairs assigns the left and right children of each dequeued parent node.
Approach 1: BFS Level-Order with Delimiters (Optimal) Implementation
Approach 1: BFS Level-Order with Delimiters (Optimal)
public class Codec {
public String serialize(TreeNode root) {
if (root == null) return "";
StringBuilder sb = new StringBuilder();
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
while (!q.isEmpty()) {
TreeNode n = q.poll();
if (n == null) sb.append("#,");
else { sb.append(n.val).append(","); q.add(n.left); q.add(n.right); }
}
return sb.toString();
}
public TreeNode deserialize(String data) {
if (data.isEmpty()) return null;
String[] vals = data.split(",");
TreeNode root = new TreeNode(Integer.parseInt(vals[0]));
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
for (int i = 1; i < vals.length; i++) {
TreeNode p = q.poll();
if (!vals[i].equals("#")) { TreeNode l = new TreeNode(Integer.parseInt(vals[i])); p.left = l; q.add(l); }
if (++i < vals.length && !vals[i].equals("#")) { TreeNode r = new TreeNode(Integer.parseInt(vals[i])); p.right = r; q.add(r); }
}
return root;
}
}class Codec {
public:
string serialize(TreeNode* root) {
if (!root) return "";
string s = ""; queue<TreeNode*> q; q.push(root);
while (!q.empty()) {
TreeNode* n = q.front(); q.pop();
if (!n) s += "#,";
else { s += to_string(n->val) + ","; q.push(n->left); q.push(n->right); }
}
return s;
}
TreeNode* deserialize(string data) {
if (data.empty()) return NULL;
vector<string> v; string cur = "";
for (char c : data) { if (c == ',') { v.push_back(cur); cur = ""; } else cur += c; }
TreeNode* root = new TreeNode(stoi(v[0]));
queue<TreeNode*> q; q.push(root);
for (int i = 1; i < v.size(); i++) {
TreeNode* p = q.front(); q.pop();
if (v[i] != "#") { TreeNode* l = new TreeNode(stoi(v[i])); p->left = l; q.push(l); }
if (++i < v.size() && v[i] != "#") { TreeNode* r = new TreeNode(stoi(v[i])); p->right = r; q.push(r); }
}
return root;
}
};class Codec:
def serialize(self, root: Optional[TreeNode]) -> str:
if not root: return ""
q, res = deque([root]), []
while q:
n = q.popleft()
if n:
res.append(str(n.val))
q.append(n.left)
q.append(n.right)
else:
res.append("#")
return ",".join(res)
def deserialize(self, data: str) -> Optional[TreeNode]:
if not data: return None
vals = data.split(",")
root = TreeNode(int(vals[0]))
q, i = deque([root]), 1
while q and i < len(vals):
p = q.popleft()
if vals[i] != "#":
p.left = TreeNode(int(vals[i]))
q.append(p.left)
i += 1
if i < len(vals) and vals[i] != "#":
p.right = TreeNode(int(vals[i]))
q.append(p.right)
i += 1
return rootfunction serialize(root) {
if (!root) return "";
const q = [root], res = [];
while (q.length) {
const n = q.shift();
if (n) { res.push(n.val); q.push(n.left); q.push(n.right); }
else { res.push("#"); }
}
return res.join(",");
}
function deserialize(data) {
if (!data) return null;
const vals = data.split(","), root = new TreeNode(+vals[0]), q = [root];
let i = 1;
while (q.length && i < vals.length) {
const p = q.shift();
if (vals[i] !== "#") { p.left = new TreeNode(+vals[i]); q.push(p.left); }
i++;
if (i < vals.length && vals[i] !== "#") { p.right = new TreeNode(+vals[i]); q.push(p.right); }
i++;
}
return root;
} Approach 2: Breadth-First Search (BFS) Level-Order with Queue
In BFS serialization, nodes are enqueued level-by-level. If a node is null, we write "null". When deserializing, split tokens and use a queue to reconstruct tree by assigning pairs of left and right children to each parent in queue.
Approach 2: Breadth-First Search (BFS) Level-Order with Queue Implementation
Approach 2: Breadth-First Search (BFS) Level-Order with Queue
public class Codec {
public String serialize(TreeNode root) {
if (root == null) return "";
Queue<TreeNode> q = new LinkedList<>();
StringBuilder sb = new StringBuilder();
q.add(root);
while (!q.isEmpty()) {
TreeNode node = q.poll();
if (node == null) {
sb.append("null,");
} else {
sb.append(node.val).append(",");
q.add(node.left);
q.add(node.right);
}
}
return sb.toString();
}
public TreeNode deserialize(String data) {
if (data == null || data.isEmpty()) return null;
String[] tokens = data.split(",");
TreeNode root = new TreeNode(Integer.parseInt(tokens[0]));
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
int i = 1;
while (!q.isEmpty() && i < tokens.length) {
TreeNode parent = q.poll();
if (!tokens[i].equals("null")) {
parent.left = new TreeNode(Integer.parseInt(tokens[i]));
q.add(parent.left);
}
i++;
if (i < tokens.length && !tokens[i].equals("null")) {
parent.right = new TreeNode(Integer.parseInt(tokens[i]));
q.add(parent.right);
}
i++;
}
return root;
}
}class Codec {
public:
string serialize(TreeNode* root) {
if (!root) return "";
string res = "";
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode* curr = q.front(); q.pop();
if (!curr) res += "null,";
else {
res += to_string(curr->val) + ",";
q.push(curr->left);
q.push(curr->right);
}
}
return res;
}
TreeNode* deserialize(string data) {
if (data.empty()) return NULL;
stringstream ss(data);
string item;
getline(ss, item, ',');
TreeNode* root = new TreeNode(stoi(item));
queue<TreeNode*> q;
q.push(root);
while (!q.empty() && getline(ss, item, ',')) {
TreeNode* parent = q.front(); q.pop();
if (item != "null") {
parent->left = new TreeNode(stoi(item));
q.push(parent->left);
}
if (getline(ss, item, ',')) {
if (item != "null") {
parent->right = new TreeNode(stoi(item));
q.push(parent->right);
}
}
}
return root;
}
};class Codec:
def serialize(self, root):
if not root: return ""
q = collections.deque([root])
res = []
while q:
node = q.popleft()
if node:
res.append(str(node.val))
q.append(node.left)
q.append(node.right)
else:
res.append("null")
return ",".join(res)
def deserialize(self, data):
if not data: return None
tokens = collections.deque(data.split(","))
root = TreeNode(int(tokens.popleft()))
q = collections.deque([root])
while q and tokens:
parent = q.popleft()
left_val = tokens.popleft()
if left_val != "null":
parent.left = TreeNode(int(left_val))
q.append(parent.left)
right_val = tokens.popleft()
if right_val != "null":
parent.right = TreeNode(int(right_val))
q.append(parent.right)
return rootvar serialize = function(root) {
if (!root) return "";
const q = [root], res = [];
while (q.length) {
const node = q.shift();
if (node) {
res.push(node.val);
q.push(node.left);
q.push(node.right);
} else {
res.push("null");
}
}
return res.join(",");
};
var deserialize = function(data) {
if (!data) return null;
const tokens = data.split(",");
const root = new TreeNode(parseInt(tokens[0]));
const q = [root];
let i = 1;
while (q.length && i < tokens.length) {
const parent = q.shift();
if (tokens[i] !== "null") {
parent.left = new TreeNode(parseInt(tokens[i]));
q.push(parent.left);
}
i++;
if (i < tokens.length && tokens[i] !== "null") {
parent.right = new TreeNode(parseInt(tokens[i]));
q.push(parent.right);
}
i++;
}
return root;
}; Approach 2: Breadth-First Search (BFS) Level-Order with Queue
In BFS serialization, nodes are enqueued level-by-level. If a node is null, we write "null". When deserializing, split tokens and use a queue to reconstruct tree by assigning pairs of left and right children to each parent in queue.
Approach 2: Breadth-First Search (BFS) Level-Order with Queue Implementation
Approach 2: Breadth-First Search (BFS) Level-Order with Queue
public class Codec {
public String serialize(TreeNode root) {
if (root == null) return "";
Queue<TreeNode> q = new LinkedList<>();
StringBuilder sb = new StringBuilder();
q.add(root);
while (!q.isEmpty()) {
TreeNode node = q.poll();
if (node == null) {
sb.append("null,");
} else {
sb.append(node.val).append(",");
q.add(node.left);
q.add(node.right);
}
}
return sb.toString();
}
public TreeNode deserialize(String data) {
if (data == null || data.isEmpty()) return null;
String[] tokens = data.split(",");
TreeNode root = new TreeNode(Integer.parseInt(tokens[0]));
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
int i = 1;
while (!q.isEmpty() && i < tokens.length) {
TreeNode parent = q.poll();
if (!tokens[i].equals("null")) {
parent.left = new TreeNode(Integer.parseInt(tokens[i]));
q.add(parent.left);
}
i++;
if (i < tokens.length && !tokens[i].equals("null")) {
parent.right = new TreeNode(Integer.parseInt(tokens[i]));
q.add(parent.right);
}
i++;
}
return root;
}
}class Codec {
public:
string serialize(TreeNode* root) {
if (!root) return "";
string res = "";
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode* curr = q.front(); q.pop();
if (!curr) res += "null,";
else {
res += to_string(curr->val) + ",";
q.push(curr->left);
q.push(curr->right);
}
}
return res;
}
TreeNode* deserialize(string data) {
if (data.empty()) return NULL;
stringstream ss(data);
string item;
getline(ss, item, ',');
TreeNode* root = new TreeNode(stoi(item));
queue<TreeNode*> q;
q.push(root);
while (!q.empty() && getline(ss, item, ',')) {
TreeNode* parent = q.front(); q.pop();
if (item != "null") {
parent->left = new TreeNode(stoi(item));
q.push(parent->left);
}
if (getline(ss, item, ',')) {
if (item != "null") {
parent->right = new TreeNode(stoi(item));
q.push(parent->right);
}
}
}
return root;
}
};class Codec:
def serialize(self, root):
if not root: return ""
q = collections.deque([root])
res = []
while q:
node = q.popleft()
if node:
res.append(str(node.val))
q.append(node.left)
q.append(node.right)
else:
res.append("null")
return ",".join(res)
def deserialize(self, data):
if not data: return None
tokens = collections.deque(data.split(","))
root = TreeNode(int(tokens.popleft()))
q = collections.deque([root])
while q and tokens:
parent = q.popleft()
left_val = tokens.popleft()
if left_val != "null":
parent.left = TreeNode(int(left_val))
q.append(parent.left)
right_val = tokens.popleft()
if right_val != "null":
parent.right = TreeNode(int(right_val))
q.append(parent.right)
return rootvar serialize = function(root) {
if (!root) return "";
const q = [root], res = [];
while (q.length) {
const node = q.shift();
if (node) {
res.push(node.val);
q.push(node.left);
q.push(node.right);
} else {
res.push("null");
}
}
return res.join(",");
};
var deserialize = function(data) {
if (!data) return null;
const tokens = data.split(",");
const root = new TreeNode(parseInt(tokens[0]));
const q = [root];
let i = 1;
while (q.length && i < tokens.length) {
const parent = q.shift();
if (tokens[i] !== "null") {
parent.left = new TreeNode(parseInt(tokens[i]));
q.push(parent.left);
}
i++;
if (i < tokens.length && tokens[i] !== "null") {
parent.right = new TreeNode(parseInt(tokens[i]));
q.push(parent.right);
}
i++;
}
return root;
}; Edge Cases to Watch
- Empty tree (returns empty string "" or "#"; deserializes to null)
- Single node tree (e.g. "1,#,#,")
- Unbalanced or zigzag trees
Master the Trees / DFS Pattern
Study the core invariant, step-by-step decision framework, and common interview traps for the Trees / DFS pattern.
How to Recognize This Pattern
- Encode/decode tree structure
- Pre-order with nulls
Complexity Analysis
Time Complexity
O(n)
Space Complexity
O(n)