Serialize and Deserialize Binary Tree in DSA Rust - Time & Space Complexity
We want to understand how long it takes to convert a binary tree into a string and back.
How does the time needed grow as the tree gets bigger?
Analyze the time complexity of the following code snippet.
// Serialize a binary tree to a string
fn serialize(root: &Option>) -> String {
fn helper(node: &Option>, s: &mut String) {
match node {
Some(n) => {
s.push_str(&n.val.to_string());
s.push(',');
helper(&n.left, s);
helper(&n.right, s);
}
None => s.push_str("#,")
}
}
let mut result = String::new();
helper(root, &mut result);
result
}
// Deserialize a string back to a binary tree
fn deserialize(data: &mut std::str::Split) -> Option> {
match data.next() {
Some("#") => None,
Some(val) => {
let node = Box::new(TreeNode {
val: val.parse().unwrap(),
left: deserialize(data),
right: deserialize(data),
});
Some(node)
}
None => None,
}
}
This code turns a binary tree into a string using preorder traversal and then rebuilds it from that string.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive traversal of each node in the tree.
- How many times: Once per node, visiting all nodes exactly one time.
Each node is visited once during serialization and once during deserialization.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 20 visits (10 nodes x 2) |
| 100 | About 200 visits |
| 1000 | About 2000 visits |
Pattern observation: The total work grows linearly with the number of nodes.
Time Complexity: O(n)
This means the time needed grows directly in proportion to the number of nodes in the tree.
[X] Wrong: "Serialization or deserialization takes constant time because it just converts data once."
[OK] Correct: Each node must be visited and processed, so time grows with tree size, not fixed.
Understanding this helps you explain how tree data can be saved and restored efficiently, a useful skill in many coding problems.
"What if we used level-order traversal instead of preorder? How would the time complexity change?"
Practice
serialize and deserialize functions for a binary tree?Solution
Step 1: Understand serialization
Serialization means converting a data structure like a binary tree into a string format that can be stored or transmitted.Step 2: Understand deserialization
Deserialization means converting the string back into the original binary tree structure.Final Answer:
Convert a binary tree to a string and back to a tree -> Option CQuick Check:
Serialize/deserialize = convert tree to string and back [OK]
- Confusing serialization with sorting
- Thinking serialization deletes nodes
- Mixing serialization with counting nodes
Option?Solution
Step 1: Check parameter type
The binary tree root is usually represented asOptionin Rust to allow empty trees and ownership.Step 2: Check return type
Serialization returns aStringrepresenting the tree.Final Answer:
fn serialize(root: Option) -> String -> Option AQuick Check:
Serialize takes Option and returns String [OK]
- Using &TreeNode instead of Option
- Returning Vec<i32> instead of String
- Using Option<TreeNode> without Box
fn serialize(root: Option) -> String {
fn helper(node: &Option, s: &mut String) {
match node {
Some(n) => {
s.push_str(&n.val.to_string());
s.push(',');
helper(&n.left, s);
helper(&n.right, s);
}
None => s.push_str("#,")
}
}
let mut result = String::new();
helper(&root, &mut result);
result
}Solution
Step 1: Understand preorder traversal serialization
The function visits root, then left subtree, then right subtree, adding values and '#' for nulls.Step 2: Trace the tree nodes
Root 1: add "1," Left child 2: add "2," Left of 2 is None: add "#," Right of 2 is None: add "#," Right child 3: add "3," Left of 3 is None: add "#," Right of 3 is None: add "#,"Final Answer:
"1,2,#,#,3,#,#," -> Option BQuick Check:
Preorder serialize = "1,2,#,#,3,#,#," [OK]
- Forgetting to add '#' for null children
- Mixing preorder with inorder or postorder
- Missing commas between values
fn deserialize(data: &str) -> Option {
fn helper(vals: &mut std::str::Split<char>) -> Option {
match vals.next() {
Some("#") => None,
Some(val) => {
let node_val = val.parse().unwrap();
Some(Box::new(TreeNode {
val: node_val,
left: helper(vals),
right: helper(vals),
}))
}
None => None,
}
}
helper(&mut data.split(","))
}Solution
Step 1: Analyze split behavior
Splitting a string like "1,2,#,#,3,#,#," by ',' produces an extra empty string at the end.Step 2: Understand impact on parsing
The helper tries to parse this empty string as a number, causing a panic or error.Final Answer:
The split iterator includes an empty string at the end causing parse errors -> Option DQuick Check:
Trailing comma causes empty string in split [OK]
- Ignoring trailing empty string from split
- Assuming parse unwrap is always safe
- Not checking for None before parsing
Solution
Step 1: Consider traversal and null markers
Preorder traversal with explicit '#' markers for nulls preserves tree structure exactly.Step 2: Consider node values and separators
Using commas as separators avoids confusion with negative or zero values; zero cannot be used as null marker.Step 3: Evaluate other options
Inorder without nulls loses structure; postorder with zero marker confuses zero values; skipping nulls in level order loses position info.Final Answer:
Use preorder traversal with '#' for nulls and commas as separators -> Option AQuick Check:
Preorder + '#' + commas = exact tree [OK]
- Using inorder without null markers loses structure
- Using zero as null marker confuses zero values
- Skipping nulls in level order loses node positions
Ready to prove it?
Pass the test and this pattern moves unknown
