Construct a BST from Preorder hands you the preorder traversal of a binary search tree and asks you to rebuild the tree. The naive approach scans for split points; the clean trick is to recurse with an upper bound and consume the array in a single pass.
Problem. Given an array preorder of distinct integers — the preorder traversal of a binary
search tree — reconstruct the tree and return its root.
Example: preorder = [8, 5, 1, 7, 10, 12] builds a BST with root 8, left subtree 5 (1, 7), and
right subtree 10 (_, 12).
The slow way first
In preorder the first value is always the root. Everything after it splits into a left part (values less than the root) followed by a right part (values greater). So you could scan the rest of the array to find where it flips from "less" to "greater," then recurse on each half.
That works, but each recursion rescans a slice to find its split point, which is O(n²) in the worst case (a skewed tree). We can do better by never rescanning.
The idea: recurse with an upper bound
Walk the array with a single shared index i. Each recursive call is told an upper bound — the largest value allowed in the subtree it is building. The call looks at preorder[i]:
- If
iis past the end, orpreorder[i]is greater than the bound, this value does not belong here — returnNonewithout consuming it. - Otherwise take
preorder[i]as the current node, advancei, then build its left child with bound = this node’s value, and its right child with the same bound it was given.
The bound is what makes the unwinding automatic: a value that is too big to fit the current range causes the recursion to return up the call stack until it finds a range loose enough to hold it.
Walk through it
Step through the animation. The index i consumes the array left to right and never moves backward. Each value drops in as a node the moment a recursive call accepts it. Watch the moments where a value like 7 or 10 is rejected by a tight bound — the recursion unwinds to a looser call, and only then does the value land.
Pseudocode
i = 0
function build(bound):
if i is past the end OR preorder[i] > bound:
return None # value does not belong in this range
node = new node with preorder[i]
i = i + 1
node.left = build(node value) # left subtree: strictly smaller
node.right = build(bound) # right subtree: same upper limit
return node
return build(+infinity) # root may be anythingThe Python solution
def bst_from_preorder(preorder):
i = 0
def build(bound):
nonlocal i
if i == len(preorder) or preorder[i] > bound:
return None
node = TreeNode(preorder[i])
i += 1
node.left = build(node.val)
node.right = build(bound)
return node
return build(float('inf'))iis a single shared cursor intopreorder;nonlocal ilets the inner function advance it.- The guard on line 6 is the whole trick: if the next value exceeds
bound, it belongs to some ancestor’s right subtree, so we return and let the recursion unwind. - After taking a node we advance
ionce — every value is consumed exactly one time. build(node.val)builds the left child under a tighter bound (everything left of a node is smaller than it).build(bound)builds the right child under the same bound the current call was given.- The first call uses
float('inf')so the root can be any value.
Complexity
| Case | Time | Notes |
|---|---|---|
| Naive (rescan for split) | O(n²) (slow) | skewed tree rescans each slice |
| Upper-bound recursion | O(n) (moderate) | each value visited once |
O(h) (moderate)Every value is read a constant number of times, so the pass is O(n). The extra space is the recursion stack, O(h) where h is the tree height — O(n) in the worst case, O(log n) when balanced.
When this pattern shows up
When you rebuild a tree from a traversal, look for a way to carry a range or bound down the recursion instead of rescanning for split points. The same upper/lower-bound idea validates a BST and reconstructs one from preorder — a shared cursor plus a bound turns an O(n²) scan into one O(n) pass.
The cursor i must be shared across all calls (here via nonlocal). If each call gets its own
copy of the index, the left and right subtrees overlap and the tree is wrong. Advance i exactly once,
right after taking a node.
Practice
Building the left subtree of node 8, the recursion has bound = 8 and i points at 7. Does 7 get taken here?
1. What does the upper bound passed to each recursive call represent?
2. Why must the index i be shared across all recursive calls?
3. When building a node's right child, what bound is passed?
4. What is the time complexity of the upper-bound approach?