Serialize and Deserialize Binary Tree

hard

Design an algorithm to serialize and deserialize a binary tree. Serialization is the process of converting a data structure into a sequence of characters so that it can be stored or transmitted and reconstructed later. There is no restriction on how your serialization and deserialization algorithms should work -- you just need to ensure that a binary tree can be serialized to a string and that this string can be deserialized back to the original tree structure.

Examples

Example 1

  • Input: root = [1,2,3,null,null,4,5]
  • Output: [1,2,3,null,null,4,5]
  • Explanation: The tree is serialized and then deserialized back to the same structure.

Example 2

  • Input: root = []
  • Output: []

Constraints

  • The number of nodes in the tree is in the range [0, 10^4].
  • -1000 <= Node.val <= 1000

Approaches

2 worked solutions ranked from straightforward to optimal — each with intuition, algorithm, complexity, and verified code in C++, Java, Python, and Go.

Approach 1

Preorder DFS

Intuition

We can serialize the tree using a preorder traversal (node, left, right), recording each node's value and using a sentinel like "null" for empty children. This captures the complete structure because knowing where null children are lets us unambiguously reconstruct the tree. During deserialization, we consume tokens one by one in the same preorder sequence: each token either creates a node (and we recursively build its left and right subtrees) or signals a null child.

Algorithm

  1. **Serialize:** Perform a preorder traversal. For each node, append its value to the result string. For null nodes, append "null". Separate tokens with commas.
  2. **Deserialize:** Split the string by commas into a list of tokens. Use a pointer or iterator to track the current position.
  3. If the current token is "null", advance the pointer and return null.
  4. Otherwise, create a new node with the token's integer value, advance the pointer, and recursively build the left and right subtrees.
TimeO(n) for both serialize and deserializeSpaceO(n)

Code (C++ · Java · Python · Go)

C++17
class Codec {
public:
    string serialize(TreeNode* root) {
        if (root == nullptr) return "null";
        return to_string(root->val) + "," + serialize(root->left) + "," + serialize(root->right);
    }

    TreeNode* deserialize(string data) {
        istringstream ss(data);
        return buildTree(ss);
    }

private:
    TreeNode* buildTree(istringstream& ss) {
        string token;
        getline(ss, token, ',');
        if (token == "null") return nullptr;
        TreeNode* node = new TreeNode(stoi(token));
        node->left = buildTree(ss);
        node->right = buildTree(ss);
        return node;
    }
};
Java 21
public class Codec {
    public String serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        serializeHelper(root, sb);
        return sb.toString();
    }

    private void serializeHelper(TreeNode node, StringBuilder sb) {
        if (node == null) {
            sb.append("null,");
            return;
        }
        sb.append(node.val).append(",");
        serializeHelper(node.left, sb);
        serializeHelper(node.right, sb);
    }

    public TreeNode deserialize(String data) {
        Queue<String> queue = new LinkedList<>(Arrays.asList(data.split(",")));
        return buildTree(queue);
    }

    private TreeNode buildTree(Queue<String> queue) {
        String val = queue.poll();
        if (val.equals("null")) return null;
        TreeNode node = new TreeNode(Integer.parseInt(val));
        node.left = buildTree(queue);
        node.right = buildTree(queue);
        return node;
    }
}
Python 3.12
def serialize(root: Optional[TreeNode]) -> str:
    result = []

    def dfs(node):
        if not node:
            result.append("null")
            return
        result.append(str(node.val))
        dfs(node.left)
        dfs(node.right)

    dfs(root)
    return ",".join(result)


def deserialize(data: str) -> Optional[TreeNode]:
    tokens = iter(data.split(","))

    def build():
        val = next(tokens)
        if val == "null":
            return None
        node = TreeNode(int(val))
        node.left = build()
        node.right = build()
        return node

    return build()
Go 1.26
import (
    "strconv"
    "strings"
)

type Codec struct{}

func Constructor() Codec {
    return Codec{}
}

func (c *Codec) serialize(root *TreeNode) string {
    var result []string
    var dfs func(*TreeNode)
    dfs = func(node *TreeNode) {
        if node == nil {
            result = append(result, "null")
            return
        }
        result = append(result, strconv.Itoa(node.Val))
        dfs(node.Left)
        dfs(node.Right)
    }
    dfs(root)
    return strings.Join(result, ",")
}

func (c *Codec) deserialize(data string) *TreeNode {
    tokens := strings.Split(data, ",")
    idx := 0
    var build func() *TreeNode
    build = func() *TreeNode {
        if idx >= len(tokens) || tokens[idx] == "null" {
            idx++
            return nil
        }
        val, _ := strconv.Atoi(tokens[idx])
        idx++
        node := &TreeNode{Val: val}
        node.Left = build()
        node.Right = build()
        return node
    }
    return build()
}
Approach 2

BFS Level Order

Intuition

An alternative approach uses breadth-first search for serialization. We process nodes level by level, recording each node's value or "null" for missing children. During deserialization, we reconstruct the tree level by level using a queue. For each non-null node we dequeue, the next two tokens represent its left and right children. This approach produces output that closely resembles the level-order array representation commonly used to describe trees.

Algorithm

  1. **Serialize:** Use a queue starting with the root. For each node, if it is not null, record its value and enqueue its children (even if null). If null, record "null".
  2. **Deserialize:** Split the string into tokens. Create the root from the first token. Use a queue to track parent nodes.
  3. For each parent in the queue, the next two tokens represent its left and right children. Create child nodes if the token is not "null" and enqueue them.
TimeO(n) for both serialize and deserializeSpaceO(n)

Code (C++ · Java · Python · Go)

C++17
class Codec {
public:
    string serialize(TreeNode* root) {
        if (root == nullptr) return "null";
        string result;
        queue<TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            TreeNode* node = q.front();
            q.pop();
            if (node == nullptr) {
                result += "null,";
            } else {
                result += to_string(node->val) + ",";
                q.push(node->left);
                q.push(node->right);
            }
        }
        return result;
    }

    TreeNode* deserialize(string data) {
        if (data == "null") return nullptr;
        istringstream ss(data);
        string token;
        getline(ss, token, ',');
        TreeNode* root = new TreeNode(stoi(token));
        queue<TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            TreeNode* node = q.front();
            q.pop();
            if (getline(ss, token, ',') && token != "null") {
                node->left = new TreeNode(stoi(token));
                q.push(node->left);
            }
            if (getline(ss, token, ',') && token != "null") {
                node->right = new TreeNode(stoi(token));
                q.push(node->right);
            }
        }
        return root;
    }
};
Java 21
public class Codec {
    public String serialize(TreeNode root) {
        if (root == null) return "null";
        StringBuilder sb = new StringBuilder();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (node == null) {
                sb.append("null,");
            } else {
                sb.append(node.val).append(",");
                queue.offer(node.left);
                queue.offer(node.right);
            }
        }
        return sb.toString();
    }

    public TreeNode deserialize(String data) {
        if (data.equals("null")) return null;
        String[] tokens = data.split(",");
        TreeNode root = new TreeNode(Integer.parseInt(tokens[0]));
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int i = 1;
        while (!queue.isEmpty() && i < tokens.length) {
            TreeNode node = queue.poll();
            if (!tokens[i].equals("null")) {
                node.left = new TreeNode(Integer.parseInt(tokens[i]));
                queue.offer(node.left);
            }
            i++;
            if (i < tokens.length && !tokens[i].equals("null")) {
                node.right = new TreeNode(Integer.parseInt(tokens[i]));
                queue.offer(node.right);
            }
            i++;
        }
        return root;
    }
}
Python 3.12
from collections import deque


def serialize(root: Optional[TreeNode]) -> str:
    if not root:
        return "null"
    result = []
    queue = deque([root])
    while queue:
        node = queue.popleft()
        if node:
            result.append(str(node.val))
            queue.append(node.left)
            queue.append(node.right)
        else:
            result.append("null")
    return ",".join(result)


def deserialize(data: str) -> Optional[TreeNode]:
    if data == "null":
        return None
    tokens = data.split(",")
    root = TreeNode(int(tokens[0]))
    queue = deque([root])
    i = 1
    while queue and i < len(tokens):
        node = queue.popleft()
        if tokens[i] != "null":
            node.left = TreeNode(int(tokens[i]))
            queue.append(node.left)
        i += 1
        if i < len(tokens) and tokens[i] != "null":
            node.right = TreeNode(int(tokens[i]))
            queue.append(node.right)
        i += 1
    return root
Go 1.26
import (
    "strconv"
    "strings"
)

type Codec struct{}

func Constructor() Codec {
    return Codec{}
}

func (c *Codec) serialize(root *TreeNode) string {
    if root == nil {
        return "null"
    }
    var result []string
    queue := []*TreeNode{root}
    for len(queue) > 0 {
        node := queue[0]
        queue = queue[1:]
        if node == nil {
            result = append(result, "null")
        } else {
            result = append(result, strconv.Itoa(node.Val))
            queue = append(queue, node.Left)
            queue = append(queue, node.Right)
        }
    }
    return strings.Join(result, ",")
}

func (c *Codec) deserialize(data string) *TreeNode {
    if data == "null" {
        return nil
    }
    tokens := strings.Split(data, ",")
    val, _ := strconv.Atoi(tokens[0])
    root := &TreeNode{Val: val}
    queue := []*TreeNode{root}
    i := 1
    for len(queue) > 0 && i < len(tokens) {
        node := queue[0]
        queue = queue[1:]
        if tokens[i] != "null" {
            v, _ := strconv.Atoi(tokens[i])
            node.Left = &TreeNode{Val: v}
            queue = append(queue, node.Left)
        }
        i++
        if i < len(tokens) && tokens[i] != "null" {
            v, _ := strconv.Atoi(tokens[i])
            node.Right = &TreeNode{Val: v}
            queue = append(queue, node.Right)
        }
        i++
    }
    return root
}

Frequently asked questions

What is the optimal time complexity of Serialize and Deserialize Binary Tree?

The most efficient approach in this guide ("BFS Level Order") runs in O(n) for both serialize and deserialize time and uses O(n) extra space. Earlier brute-force approaches are slower; see all 2 approaches above for the full progression.

What pattern does Serialize and Deserialize Binary Tree use?

Serialize and Deserialize Binary Tree is a hard-level Trees problem involving Tree, Binary Tree, Depth-First Search. Recognizing the pattern is half the work — see the intuition section above for how to spot it.

Can Serialize and Deserialize Binary Tree be solved without extra space?

The most space-efficient approach in this guide uses O(n) extra space (excluding the input). If you're aiming for in-place — see the trade-off in the algorithm section above — the brute-force approach uses O(n), while the optimized version uses O(n).

Which programming languages does this Ratta solution support?

Every approach above ships with verified, runnable solutions in C++, Java, Python, and Go. The starter templates in the workspace match the same four languages so you can practice in your interview language of choice.

Original problem: leetcode.com

Loading editor...
root=
[1,2,3,null,null,4,5]
[1,2,3,null,null,4,5]