DSA DSA In Depth
0%
lesson tree-serializationsuccinct-treesreconstruction

Unique Tree Reconstruction (Preorder + Inorder) & Succinct Bit-String Serialization

7 min read Module 3.1: Binary Trees, Traversals, BSTs & Priority Heaps Updated Oct 23, 2026
📄 Cheat Sheet
Construct Binary Tree from Preorder and Inorder Traversal
NeetCode Construct Binary Tree from Preorder and Inorder Traversal Watch on YouTube ↗

Clear breakdown of how Pre-Order identifies subtree roots while In-Order partitions left and right subtrees, plus linear-time index map optimization.

0:00 / --:--

The Reconstruction Matrix: Which Traversal Pairs Uniquely Identify a Binary Tree?

Assume a binary tree has $N$ distinct keys. When we attempt to reconstruct the tree recursively from two traversal arrays:

  • Pre-Order + In-Order (Uniquely Reconstructible in $O(N)$): The first element of Pre-Order is unconditionally the root $r$. Looking up $r$'s index in the In-Order array immediately tells us the exact size $k$ of the left subtree (all elements to the left of $r$) and the size $N - 1 - k$ of the right subtree (all elements to the right of $r$).
  • Post-Order + In-Order (Uniquely Reconstructible in $O(N)$): Symmetric—the last element of Post-Order is the root $r$, and In-Order partitions the left and right subtrees.
  • Pre-Order + Post-Order (Ambiguous for General Binary Trees!): Pre-Order gives $[r, \text{Left}, \text{Right}]$ and Post-Order gives $[\text{Left}, \text{Right}, r]$. If a node $u$ has only one child $v$, both Pre-Order ($[u, v]$) and Post-Order ($[v, u]$) are identical whether $v$ is a left child or a right child! However, if the tree is a Full (Strict) Binary Tree—where every node has either $0$ or $2$ children—then Pre-Order + Post-Order does uniquely reconstruct the tree because whenever $u$ is not a leaf, the element immediately following $u$ in Pre-Order is guaranteed to be $u$'s left child, and its position in Post-Order marks the exact end of the left subtree.
Reconstruction / Serialization Method Time Complexity Auxiliary / Wire Space Uniqueness Condition
Pre-Order + In-Order (Naive Linear Scan) O(N²) Skewed 2N Words Distinct keys required
Pre-Order + In-Order (Hash Map / Implicit Stack) O(N) Guaranteed 2N Words Distinct keys required
Pre-Order + Post-Order O(N) 2N Words Full Binary Trees (0 or 2 children) ONLY
Pre-Order with Null Sentinels / Jacobson Bitvector O(N) 2N + 1 Struct Bits + N Keys Works for ANY tree (even duplicate keys)

Information-Theoretic Lower Bound: Catalan Enumeration & Jacobson's $2N + 1$ Bit Bound

The number of structurally distinct binary trees on $N$ nodes is the $N$-th Catalan number:

$$C_N = \frac{1}{N + 1}\binom{2N}{N} \sim \frac{4^N}{\sqrt{\pi} N^{3/2}}$$

By Shannon's source coding lower bound, any lossless binary encoding of an arbitrary $N$-node binary tree topology requires at least:

$$\lceil \log_2 C_N \rceil = 2N - \frac{3}{2}\log_2 N - O(1) \text{ bits}$$

Succinct Pre-Order Bit-String: Replace every internal node with a 1 bit and every null external child pointer ($N + 1$ of them) with a 0 bit during a Pre-Order walk. This produces a self-delimiting bitstring of length $N + (N + 1) = 2N + 1$ bits (plus an array of $N$ payload values in Pre-Order), achieving the information-theoretic minimum up to lower-order terms.

Morris O(1)-Space Threaded In-Order & Euler Tour Subtree Flattening
O(N) Time · O(1) Aux Space / Contiguous [tin, tout]

Trace how Morris Traversal temporarily rewires each in-order predecessor’s null right pointer (pred.right = curr) to climb back up without a call stack, and how Euler Tour timestamps flatten every subtree into a contiguous array slice [tin[u], tout[u]].

Step 1 / 9
Custom Input:
Timeline

Invariant & State

Phase 1 (Create Thread): At curr=4, in-order predecessor in left subtree is pred=3. Since pred.right == null, set pred.right = curr(4) and advance curr = curr.left(2).
curr=4 2 5 1 pred=3 Thread: pred(3).right = curr(4)
Pointer Action
pred(3).right = curr(4)
Thread State
1 Active Thread (3 → 4)
In-Order Output
[]
Aux Call-Stack Space
O(1) Words
[Step 1] Phase 1 (Create Thread): At curr=4, in-order predecessor in left subtree is pred=3. Since pred.right == null, set pred.right = curr(4) and advance curr = curr.left(2).

Visualizing Subtree Partitioning & Succinct Bit Encoding

Pre-Order: [Root][ Left Subtree (k=3) ][ Right (2) ] 3 9 1 4 20 7 In-Order: [ Left Subtree (k=3) ][Root][ Right (2) ] 1 9 4 3 20 7 Succinct Pre-Order Bitstream (1=Node, 0=Null, 2N+1 = 13 bits): Bits: 1 1 1 0 0 1 0 0 1 0 1 0 0 | Keys: [3, 9, 1, 4, 20, 7] Ambiguity Counterexample (Pre + Post): Tree 1: A.left = B Tree 2: A.right = B Pre(T1) = [A, B] Pre(T2) = [A, B] Post(T1) = [B, A] Post(T2) = [B, A] → Indistinguishable unless tree is Full!
flowchart TD A["build(preL, preR, inL, inR)"] --> B{"preL > preR ?"} B -- "Yes" --> C["return nullptr"] B -- "No" --> D["rootVal = pre[preL] mid = inIndexMap[rootVal] leftSize = mid - inL"] D --> E["root.left = build(preL+1, preL+leftSize, inL, mid-1)"] D --> F["root.right = build(preL+leftSize+1, preR, mid+1, inR)"]

Hardware Reality: Pointer-Free Succinct Trees (Jacobson LOUDS)

A standard 64-bit pointer-based tree with $10^7$ nodes requires $10^7 \times 24\text{ B} = 240\text{ MB}$ of RAM. Using Guy Jacobson's LOUDS (Level-Order Unary Degree Sequence) succinct encoding, the entire tree topology is stored in $2N + 1$ bits ($2.5\text{ MB}$)—a $96\times$ structural compression ratio! Child and parent navigation replace pointer dereferences with hardware POPCNT-accelerated $\text{rank}_1(i)$ and $\text{select}_0(i)$ bitvector queries that execute entirely inside L2/L3 cache.

Implementation: $O(N)$ Hash-Free Boundary Reconstruction & Bitstream Codec

Even without a hash map, we can reconstruct a binary tree from Pre-Order and In-Order in strict $O(N)$ time by passing the upper-bound sentinel key stopVal down the recursion (or using an explicit stack):

// C++20: O(N) Zero-HashMap Reconstruction from Pre-Order + In-Order using Boundary Sentinel
#include <vector>
#include <limits>

struct TreeNode {
    int val;
    TreeNode *left = nullptr, *right = nullptr;
    explicit TreeNode(int v) : val(v) {}
};

class TreeReconstructor {
    size_t preIdx = 0;
    size_t inIdx = 0;

    TreeNode* buildBoundary(const std::vector<int>& pre, const std::vector<int>& in, long long stopVal) {
        if (preIdx >= pre.size()) return nullptr;
        if (in[inIdx] == stopVal) {
            ++inIdx; // Reached the ancestor boundary in In-Order: left/right subtree complete
            return nullptr;
        }
        int rootVal = pre[preIdx++];
        TreeNode* root = new TreeNode(rootVal);
        // Left subtree stops when In-Order hits rootVal
        root->left = buildBoundary(pre, in, rootVal);
        // Right subtree stops when In-Order hits our parent's stopVal
        root->right = buildBoundary(pre, in, stopVal);
        return root;
    }

public:
    TreeNode* buildTree(const std::vector<int>& preorder, const std::vector<int>& inorder) {
        preIdx = 0;
        inIdx = 0;
        return buildBoundary(preorder, inorder, std::numeric_limits<long long>::max());
    }
};

Failure Modes & Adversarial Pitfalls

  • Duplicate Keys Break Pre-Order + In-Order Reconstruction: If a tree contains duplicate keys (e.g., two nodes with value 5), looking up 5 in the In-Order array is ambiguous: Tree A (root 5 with left child 5) and Tree B (root 5 with right child 5) both produce Pre-Order [5, 5] and In-Order [5, 5]. Always use explicit null sentinels or succinct bitvectors when keys are not guaranteed unique.
  • Subarray Slicing Memory Blowup: Passing sliced copies (preorder[1:k+1]) in Python or JavaScript turns an $O(N)$ algorithm into $O(N^2)$ time and $O(N^2)$ memory on skewed trees. Always pass index bounds $(preL, preR, inL, inR)$ or use the boundary sentinel pointer approach above.
Check yourself Question 1 of 4
Concept Check Information-Theoretic Minimum Bits to Encode Binary Tree Topology recall

By the Catalan number bound C_N = (1/(N+1)) * C(2N, N) ≈ 4^N / (√π N^(3/2)), what is the asymptotic information-theoretic minimum number of bits required to represent the structure of an arbitrary N-node binary tree?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

Why does Pre-Order + In-Order reconstruction fail when a binary tree contains duplicate keys (e.g., two nodes with value 7), and how do production serializers resolve this in O(N) time and minimal space?

Key Takeaways

By the Catalan enumeration C_N = (1/(N+1)) C(2N, N), there are Θ(4^N / N^(3/2)) distinct binary trees of size N, requiring at least 2N - Θ(log N) bits to encode topology.
Pre-Order + In-Order and Post-Order + In-Order uniquely reconstruct any binary tree with distinct keys in O(N) time.
Pre-Order + Post-Order cannot distinguish a lone left child from a lone right child, and only uniquely reconstructs Full Binary Trees (where every node has 0 or 2 children).
A single Pre-Order walk augmented with 1/0 topology bits (2N + 1 bits total) reconstructs any tree in O(N) time even with duplicate keys.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue