Serialize and Deserialize Binary Tree
Preorder serialization with null markers; deserialize by recursively consuming the next token.
4 min read
tree dfs design serialization
Problem#
Design an algorithm to serialize a binary tree into a string and deserialize it back.
Examples#
[1,2,3,null,null,4,5]→"1,2,#,#,3,4,#,#,5,#,#"(preorder with null markers).[]→"#".
Constraints#
- The number of nodes is in
[0, 10^4]. Values are in[-1000, 1000].
Approach#
Serialize via preorder traversal: emit value, then recurse on left and right; emit # for null. Deserialize: split on comma, consume tokens with a cursor — # returns null, otherwise create a node and recurse for left then right.
Solution#
class Codec {public: string serialize(TreeNode* root) { string s; function<void(TreeNode*)> dfs = [&](TreeNode* u) { if (!u) { s += "#,"; return; } s += to_string(u->val) + ","; dfs(u->left); dfs(u->right); }; dfs(root); return s; }
TreeNode* deserialize(string data) { int i = 0; function<TreeNode*()> build = [&]() -> TreeNode* { int j = data.find(',', i); string tok = data.substr(i, j - i); i = j + 1; if (tok == "#") return nullptr; TreeNode* node = new TreeNode(stoi(tok)); node->left = build(); node->right = build(); return node; }; return build(); }};import ( "strconv" "strings")
type Codec struct{}
func Constructor() Codec { return Codec{} }
func (c *Codec) serialize(root *TreeNode) string { var sb strings.Builder var dfs func(u *TreeNode) dfs = func(u *TreeNode) { if u == nil { sb.WriteString("#,") return } sb.WriteString(strconv.Itoa(u.Val)) sb.WriteByte(',') dfs(u.Left) dfs(u.Right) } dfs(root) return sb.String()}
func (c *Codec) deserialize(data string) *TreeNode { i := 0 var build func() *TreeNode build = func() *TreeNode { j := strings.IndexByte(data[i:], ',') + i tok := data[i:j] i = j + 1 if tok == "#" { return nil } v, _ := strconv.Atoi(tok) node := &TreeNode{Val: v} node.Left = build() node.Right = build() return node } return build()}from collections import dequefrom typing import Optional
class Codec: def serialize(self, root: Optional[TreeNode]) -> str: parts = [] def dfs(u: Optional[TreeNode]) -> None: if not u: parts.append('#') return parts.append(str(u.val)) dfs(u.left) dfs(u.right) dfs(root) return ','.join(parts)
def deserialize(self, data: str) -> Optional[TreeNode]: tokens = deque(data.split(',')) def build() -> Optional[TreeNode]: tok = tokens.popleft() if tok == '#': return None node = TreeNode(int(tok)) node.left = build() node.right = build() return node return build()function serialize(root) { const parts = []; const dfs = (u) => { if (!u) { parts.push('#'); return; } parts.push(String(u.val)); dfs(u.left); dfs(u.right); }; dfs(root); return parts.join(',');}
function deserialize(data) { const tokens = data.split(','); let i = 0; const build = () => { const tok = tokens[i++]; if (tok === '#') return null; const node = new TreeNode(parseInt(tok, 10)); node.left = build(); node.right = build(); return node; }; return build();}public class Codec { public String serialize(TreeNode root) { StringBuilder sb = new StringBuilder(); ser(root, sb); return sb.toString(); }
private void ser(TreeNode u, StringBuilder sb) { if (u == null) { sb.append("#,"); return; } sb.append(u.val).append(','); ser(u.left, sb); ser(u.right, sb); }
public TreeNode deserialize(String data) { Deque<String> tokens = new ArrayDeque<>(Arrays.asList(data.split(","))); return build(tokens); }
private TreeNode build(Deque<String> tokens) { String tok = tokens.poll(); if (tok == null || tok.equals("#")) return null; TreeNode node = new TreeNode(Integer.parseInt(tok)); node.left = build(tokens); node.right = build(tokens); return node; }}function serialize(root: TreeNode | null): string { const parts: string[] = []; const dfs = (u: TreeNode | null): void => { if (u === null) { parts.push('#'); return; } parts.push(String(u.val)); dfs(u.left); dfs(u.right); }; dfs(root); return parts.join(',');}
function deserialize(data: string): TreeNode | null { const tokens: string[] = data.split(','); let i = 0; const build = (): TreeNode | null => { const tok = tokens[i++]; if (tok === '#') return null; const node = new TreeNode(parseInt(tok, 10)); node.left = build(); node.right = build(); return node; }; return build();}Editorial#
Preorder + null markers uniquely encodes the tree because every recursive call consumes exactly one token (either a node or a null marker). The cursor i is a shared state across recursive calls — equivalent to streaming the tokens.
Complexity#
- Time: O(n).
- Space: O(n).
Concept revision#
Related#