Serialize and Deserialize Binary Tree - JS Solution

JS
Editorial

Solution explanation

Solution explanation

Approach

We use a preorder traversal (node → left → right) with explicit null markers (#) to serialize, and a single moving index over the token stream to deserialize. This pairing guarantees a unique, unambiguous reconstruction with a simple, one-pass algorithm.

🎯 Goals

  • Serialization: Produce a linear stream that faithfully encodes both the node values and the tree’s shape.
  • Deserialization: Consume the stream token-by-token to rebuild the original tree exactly.

🧠 Why Preorder + Null Markers?

  • Preorder always lists the parent before its subtrees. That means every time we see a non-# token, it’s the root of the current subtree; immediately afterwards, the stream contains its entire left subtree, then its entire right subtree.
  • Explicit nulls (#) ensure shape is preserved. Without them, multiple different trees could serialize to the same sequence.
  • Together, these properties let us deserialize with a single pointer that advances once per token, consuming all of the left first and then all of the right for each subtree—no ambiguity, no re-reading.

🗂️ Serialization (DFS Preorder)

What we do: We perform a recursive DFS. At each node:

  1. If the node is null → append # to the stream. Why? The # is a structural placeholder that prevents “phantom” nodes during reconstruction and keeps the encoding unambiguous.
  2. If the node exists → append its value to the stream first (preorder “visit root first”). Why? Putting the root before its children gives deserialization a clear entry point for this subtree.
  3. Then recurse left fully, then right fully. Why? Preorder’s contract guarantees the stream will contain the left subtree’s complete encoding immediately after the root, followed by the right subtree. This exact ordering is what deserialization relies on.

Wrap-up: The result is a comma-separated preorder stream containing values and # markers. It encodes both values and shape, so it can be consumed to rebuild the exact original tree.


🧩 Deserialization (Consume Stream with a Moving Index)

Setup: Split the serialized string on commas to get an array values. Maintain an integer index that starts at 0 and always points to the next token to read. Each call to buildTree():

  • Reads exactly one token from values[index] and then advances index by 1.

Algorithm per subtree (function buildTree(path)):

  1. Read the next token (at values[index], then increment index).
  2. If token is # → return null. Why? This position is an absent child; explicit nulls keep shape faithful.
  3. Otherwise, token is a value → create a node with that value; this is the root of the current subtree.
  4. Recurse to build the left subtree: Why? In preorder, right after the root token comes the entire left subtree (values and # markers). The recursive call consumes all its tokens internally. When it returns, the index naturally sits at the start of the right subtree.
  5. Recurse to build the right subtree: Why? The next unread token now begins the right subtree, again in preorder.
  6. Return the constructed node.

Important invariants & rationale:

  • Left fully before right: Because serialization recorded all left before any right, deserialization consumes tokens in the same order.
  • One-pass pointer: The single index ensures tokens are neither reused nor skipped; each recursive call consumes exactly one token on entry.

✅ Correctness & Uniqueness

  • Preorder puts each root before its children, so every subtree has a clear starting token.
  • Null markers ensure shape is encoded; there is only one tree that matches the stream.
  • Single moving index consumes tokens in order; left is completed before right—no ambiguity remains.

Time Complexity

O(n), where n is the number of actual nodes in the tree. Each node and its # markers for missing children are processed once during both serialization and deserialization. Why still O(n)? The number of # markers grows only at most a constant rate relative to nodes, so the total work remains linear.

Space Complexity

O(n) for the output stream, plus O(h) for the recursion stack, where h is the height of the tree (height is equal to the number of nodes in the worst case). Therefore, the overall space complexity is O(n).

Visucodize editorial solution titled JS Solution for LeetCode's Serialize and Deserialize Binary Tree coding problem. Includes code and may include explanation. You can animate the code step by step using the provided input by clicking the run button, or fork it locally to update the code and input for custom visualization. View the original problem at https://leetcode.com/problems/serialize-and-deserialize-binary-tree.