Skip to content
DSAPrep

    ↑↓ to move · Enter to open · Esc to close

    HardTrees

    Serialize And Deserialize Binary Tree

    Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

    Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.

    Clarification: The input/output format used here is just for illustration purposes, you do not necessarily need to follow this format, so feel free to come up with a different approach.

    Example 1

    Input: root = [1,2,3,null,null,4,5]

    Output: [1,2,3,null,null,4,5]

    Example 2

    Input: root = []

    Output: []

    Constraints

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

    Serializing a tree needs to preserve shape, not just values — so None children must be encoded explicitly, not just dropped, or the deserializer would not know where each subtree ends. Any traversal order works as long as encode and decode agree on it; a pre-order DFS makes decoding especially clean because each value is immediately followed by everything needed to rebuild its own subtree.

    Preorder DFS with Null Markers

    OptimalTime O(n)Space O(n)

    Serialize: walk the tree in preorder (node, then left, then right), appending each value to a list, and appending a sentinel ('#') wherever a child is None. Join with commas into one string.

    Deserialize: split the string back into tokens and consume them with an iterator, in the same preorder order they were written: read one token as the current node’s value (or None if it is the sentinel), then recursively build its left subtree, then its right — the iterator’s position naturally stays in sync because both sides visit nodes in the same order.

    class TreeNode:
    def __init__(self, val=0, left=None, right=None):
    self.val = val
    self.left = left
    self.right = right
    class Codec:
    def serialize(self, root: TreeNode | None) -> str:
    values = []
    def dfs(node: TreeNode | None) -> None:
    if node is None:
    values.append("#")
    return
    values.append(str(node.val))
    dfs(node.left)
    dfs(node.right)
    dfs(root)
    return ",".join(values)
    def deserialize(self, data: str) -> TreeNode | None:
    tokens = iter(data.split(","))
    def build() -> TreeNode | None:
    token = next(tokens)
    if token == "#":
    return None
    node = TreeNode(int(token))
    node.left = build()
    node.right = build()
    return node
    return build()

    Serialization turns that tree into the string 1,2,#,#,3,4,#,#,5,#,#. The harder half is decoding that string back into an identical tree — trace it below. Watch the single moving pointer eat one token at a time: each number spawns a node, and each # closes a subtree so the pointer falls back to the sibling or parent. That one pass over the string, in the same preorder order the serializer wrote, is enough to rebuild the exact shape.

    Reading the token string

    12##34##5##

    Tree being rebuilt

    1 / 12
    comparingseenresultcurrent

    The pointer reads `1,2,#,#,3,4,#,#,5,#,#` one token at a time. A `#` closes a subtree; a number spawns a node.

    Deserializing reads that same string left to right: 1 becomes the root, then the next token (2) becomes its left child; since 2’s own next two tokens are both #, 2 is a leaf, and the reader falls back to 1’s right child, reading 3, and so on — the comma-separated order alone is enough to rebuild the exact shape.

    Complexity: both serialize and deserialize visit every node (and every None child slot) exactly once, doing O(1) work each → O(n) time for each operation. The output string and the recursion stack both hold O(n) elements → O(n) space.

    BFS Level-Order with Null Markers

    Time O(n)Space O(n)

    An alternative that mirrors LeetCode’s own display format: serialize with a queue, level by level, emitting '#' for None children (but not recursing into them). Deserialize the same way, pulling two tokens per queue entry to attach as left/right children.

    from collections import deque
    class Codec:
    def serialize(self, root: TreeNode | None) -> str:
    if root is None:
    return ""
    values = []
    queue = deque([root])
    while queue:
    node = queue.popleft()
    if node is None:
    values.append("#")
    continue
    values.append(str(node.val))
    queue.append(node.left)
    queue.append(node.right)
    return ",".join(values)
    def deserialize(self, data: str) -> TreeNode | None:
    if not data:
    return None
    tokens = data.split(",")
    root = TreeNode(int(tokens[0]))
    queue = deque([root])
    i = 1
    while queue:
    node = queue.popleft()
    if tokens[i] != "#":
    node.left = TreeNode(int(tokens[i]))
    queue.append(node.left)
    i += 1
    if tokens[i] != "#":
    node.right = TreeNode(int(tokens[i]))
    queue.append(node.right)
    i += 1
    return root

    Why consider this: same O(n) time / O(n) space bounds as the DFS version, but the resulting string reads level by level, which is easier for a human to eyeball and matches the array format LeetCode itself uses to describe trees. The DFS version is generally preferred in practice for its shorter, purely recursive implementation.

    More Trees practice