🧠
Iterative Serialization and Deserialization Using Stack
💡 This approach avoids recursion by using an explicit stack to perform preorder traversal for serialization and a stack-based method for deserialization. It helps understand iterative tree traversal and can prevent stack overflow on very deep trees.
Intuition
Use a stack to simulate preorder traversal. Push nodes and record values or null markers. For deserialization, use a stack to reconstruct the tree iteratively by tracking nodes and their children.
Algorithm
- Serialization: Use a stack to traverse the tree preorder.
- Pop node from stack, append its value or 'X' if null.
- Push right then left child to stack to maintain preorder order.
- Deserialization: Use a queue of values from serialized string.
- Iteratively reconstruct tree nodes using a stack to track parents and assign children.
💡 The main difficulty is managing the stack correctly to maintain preorder order and reconstruct the tree without recursion.
class Codec:
def serialize(self, root):
if not root:
return 'X'
stack = [root]
vals = []
while stack:
node = stack.pop()
if node:
vals.append(str(node.val))
stack.append(node.right)
stack.append(node.left)
else:
vals.append('X')
return ','.join(vals)
def deserialize(self, data):
vals = data.split(',')
if vals[0] == 'X':
return None
root = TreeNode(int(vals[0]))
stack = [(root, 0)] # node and child index (0=left,1=right)
i = 1
while stack:
node, child_idx = stack[-1]
if i >= len(vals):
break
val = vals[i]
i += 1
if val != 'X':
child = TreeNode(int(val))
if child_idx == 0:
node.left = child
else:
node.right = child
stack[-1] = (node, child_idx + 1)
stack.append((child, 0))
else:
if child_idx == 0:
node.left = None
stack[-1] = (node, 1)
else:
node.right = None
stack.pop()
return root
# Example usage:
# codec = Codec()
# s = codec.serialize(root)
# root = codec.deserialize(s)
Line Notes
if not root:Handle empty tree edge case immediately
stack = [root]Initialize stack with root for iterative traversal
node = stack.pop()Process current node from stack
if node:If node exists, record value and push children
stack.append(node.right)Push right child first to process left child next
stack.append(node.left)Push left child to stack
vals.append('X')Mark null nodes explicitly
vals = data.split(',')Split serialized string for deserialization
stack = [(root, 0)]Stack holds nodes and which child to assign next
if val != 'X':Create child node if not null marker
stack[-1] = (node, child_idx + 1)Update child index after assigning a child
stack.pop()Pop node when both children assigned
import java.util.*;
public class Codec {
private static final String NULL = "X";
private static final String SEP = ",";
public String serialize(TreeNode root) {
if (root == null) return NULL;
StringBuilder sb = new StringBuilder();
Deque<TreeNode> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
if (node == null) {
sb.append(NULL).append(SEP);
continue;
}
sb.append(node.val).append(SEP);
stack.push(node.right);
stack.push(node.left);
}
return sb.toString();
}
public TreeNode deserialize(String data) {
if (data.equals(NULL)) return null;
String[] vals = data.split(SEP);
TreeNode root = new TreeNode(Integer.parseInt(vals[0]));
Deque<Pair<TreeNode, Integer>> stack = new ArrayDeque<>();
stack.push(new Pair<>(root, 0));
int i = 1;
while (!stack.isEmpty()) {
Pair<TreeNode, Integer> top = stack.peek();
TreeNode node = top.getKey();
int childIdx = top.getValue();
if (i >= vals.length) break;
String val = vals[i++];
if (!val.equals(NULL)) {
TreeNode child = new TreeNode(Integer.parseInt(val));
if (childIdx == 0) node.left = child;
else node.right = child;
stack.pop();
stack.push(new Pair<>(node, childIdx + 1));
stack.push(new Pair<>(child, 0));
} else {
if (childIdx == 0) {
node.left = null;
stack.pop();
stack.push(new Pair<>(node, 1));
} else {
node.right = null;
stack.pop();
}
}
}
return root;
}
// TreeNode and Pair classes
public static class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) { this.val = val; }
}
public static class Pair<K,V> {
private K key;
private V value;
public Pair(K key, V value) { this.key = key; this.value = value; }
public K getKey() { return key; }
public V getValue() { return value; }
public void setValue(V value) { this.value = value; }
}
// Example usage:
// Codec codec = new Codec();
// String s = codec.serialize(root);
// TreeNode root = codec.deserialize(s);
}
Line Notes
if (root == null) return NULL;Handle empty tree quickly
Deque<TreeNode> stack = new ArrayDeque<>();Use stack for iterative preorder traversal
stack.push(root);Initialize stack with root node
TreeNode node = stack.pop();Process current node from stack
if (node == null)If node is null, append null marker
sb.append(NULL).append(SEP);Append null marker for missing nodes
sb.append(node.val).append(SEP);Append current node value and separator
stack.push(node.right);Push right child first to process left child next
stack.push(node.left);Push left child to stack
String[] vals = data.split(SEP);Split serialized string for deserialization
Deque<Pair<TreeNode, Integer>> stack = new ArrayDeque<>();Stack holds nodes and child index to assign
if (!val.equals(NULL)) {Create child node if not null marker
stack.pop(); stack.push(new Pair<>(node, childIdx + 1));Update child index after assigning a child
stack.push(new Pair<>(child, 0));Push new child node to stack for further assignment
stack.pop();Pop node when both children assigned
#include <iostream>
#include <string>
#include <sstream>
#include <stack>
#include <queue>
using namespace std;
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Codec {
public:
string serialize(TreeNode* root) {
if (!root) return "X";
stack<TreeNode*> stk;
stk.push(root);
string res;
while (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
if (!node) {
res += "X,";
continue;
}
res += to_string(node->val) + ",";
stk.push(node->right);
stk.push(node->left);
}
return res;
}
TreeNode* deserialize(const string& data) {
if (data == "X") return nullptr;
istringstream in(data);
string val;
vector<string> vals;
while (getline(in, val, ',')) {
vals.push_back(val);
}
TreeNode* root = new TreeNode(stoi(vals[0]));
stack<pair<TreeNode*, int>> stk;
stk.push({root, 0});
int i = 1;
while (!stk.empty()) {
auto& [node, childIdx] = stk.top();
if (i >= (int)vals.size()) break;
val = vals[i++];
if (val != "X") {
TreeNode* child = new TreeNode(stoi(val));
if (childIdx == 0) node->left = child;
else node->right = child;
stk.top().second++;
stk.push({child, 0});
} else {
if (childIdx == 0) {
node->left = nullptr;
stk.top().second++;
} else {
node->right = nullptr;
stk.pop();
}
}
}
return root;
}
};
// Example usage:
// Codec codec;
// string s = codec.serialize(root);
// TreeNode* root = codec.deserialize(s);
Line Notes
if (!root) return "X";Handle empty tree edge case
stack<TreeNode*> stk;Stack for iterative preorder traversal
stk.push(root);Initialize stack with root node
TreeNode* node = stk.top();Process current node from stack
stk.pop();Pop node when both children assigned
if (!node)If node is null, append null marker
res += "X,";Append null marker for missing nodes
res += to_string(node->val) + ",";Append current node value and comma
stk.push(node->right);Push right child first to process left child next
stk.push(node->left);Push left child to stack
vector<string> vals;Store split serialized values for deserialization
stack<pair<TreeNode*, int>> stk;Stack holds nodes and child index to assign
if (val != "X") {Create child node if not null marker
stk.top().second++;Increment child index after assigning a child
stk.push({child, 0});Push new child node to stack for further assignment
class TreeNode {
constructor(val = 0, left = null, right = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
class Codec {
serialize(root) {
if (!root) return 'X';
const stack = [root];
const vals = [];
while (stack.length > 0) {
const node = stack.pop();
if (node === null) {
vals.push('X');
continue;
}
vals.push(node.val.toString());
stack.push(node.right);
stack.push(node.left);
}
return vals.join(',');
}
deserialize(data) {
if (data === 'X') return null;
const vals = data.split(',');
const root = new TreeNode(parseInt(vals[0]));
const stack = [{node: root, childIdx: 0}];
let i = 1;
while (stack.length > 0) {
const top = stack[stack.length - 1];
if (i >= vals.length) break;
const val = vals[i++];
if (val !== 'X') {
const child = new TreeNode(parseInt(val));
if (top.childIdx === 0) top.node.left = child;
else top.node.right = child;
top.childIdx++;
stack.push({node: child, childIdx: 0});
} else {
if (top.childIdx === 0) {
top.node.left = null;
top.childIdx++;
} else {
top.node.right = null;
stack.pop();
}
}
}
return root;
}
}
// Example usage:
// const codec = new Codec();
// const s = codec.serialize(root);
// const root = codec.deserialize(s);
Line Notes
if (!root) return 'X';Handle empty tree edge case
const stack = [root];Initialize stack for iterative preorder traversal
const node = stack.pop();Process current node from stack
if (node === null)If node is null, append null marker
vals.push('X');Mark null nodes explicitly
vals.push(node.val.toString());Add current node's value as string
stack.push(node.right);Push right child first to process left child next
stack.push(node.left);Push left child to stack
const vals = data.split(',');Split serialized string for deserialization
const stack = [{node: root, childIdx: 0}];Stack holds nodes and which child to assign next
if (val !== 'X') {Create child node if not null marker
top.childIdx++;Increment child index after assigning a child
stack.push({node: child, childIdx: 0});Push new child node to stack for further assignment
stack.pop();Pop node when both children assigned
Each node is processed once during serialization and deserialization. The stack and output string scale linearly with the number of nodes.
💡 For 20 nodes, this means roughly 60 operations, similar to recursive approach but avoids call stack overhead.
Interview Verdict: Accepted
This iterative approach is accepted and useful when recursion depth is a concern or interviewer requests iterative solutions.