Topics › Binary Search Trees › Practice Problems
Construct a BST from a preorder traversal
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an array preorder of distinct integers that is the preorder traversal (node, then left subtree, then right subtree) of some binary search tree. Build that BST and return its root.
The answer is unique. The first key of a preorder is the root; the keys smaller than it are exactly the left subtree and the keys larger than it are the right subtree, and the same argument applies recursively inside each part. Constraints (LeetCode): 1 ≤ n ≤ 100, keys distinct. The code below also accepts an empty array and returns an empty tree.
Trees are shown in LeetCode level order, with null for a missing child.
Example 1
preorder = [8, 5, 1, 7, 10, 12]
8
/ \
5 10
/ \ \
1 7 12
answer = [8, 5, 10, 1, 7, null, 12]
8 is the root; 5, 1, 7 are smaller, so they form the left subtree; 10, 12 form the right subtree.
Example 2
preorder = [1, 3]
answer = [1, null, 3] // 3 > 1, so it can only be the right child
Approach 1: Brute force, insert one key at a time
Intuition. The simplest way to build a BST is ordinary insertion: start with an empty tree and insert the keys in the given order. The surprising part is that inserting in preorder order reproduces exactly the target tree. Preorder lists every node before its descendants, so when a key is inserted, all of its ancestors in the target tree are already present. Insertion walks down by comparing with those ancestors, which are precisely the comparisons that define where the key belongs, so it lands in its correct slot.
Algorithm.
- Start with
root = null. - For each key
vinpreorder, walk from the root: go left ifvis smaller than the current key, right otherwise, until the needed child is empty. - Attach a new node for
vthere.
static TreeNode bruteForce(int[] preorder) {
TreeNode root = null;
for (int v : preorder) root = insert(root, v);
return root;
}
static TreeNode insert(TreeNode root, int v) {
TreeNode node = new TreeNode(v);
if (root == null) return node;
TreeNode cur = root;
while (true) {
if (v < cur.val) {
if (cur.left == null) { cur.left = node; break; }
cur = cur.left;
} else {
if (cur.right == null) { cur.right = node; break; }
cur = cur.right;
}
}
return root;
}
Complexity. Time O(n^2) in the worst case. Each insertion costs O(h), and for sorted input such as [1, 2, 3, 4, 5] the tree is a chain, so the i-th insertion walks i nodes. For a random-looking preorder the tree is balanced and it is O(n log n). Space O(1) beyond the tree itself, because insertion is iterative.
Approach 2: Better, preorder plus inorder
Intuition. The in-order traversal of any BST is just its keys in sorted order. So we can manufacture the inorder by sorting a copy of the preorder, and then use the standard “construct a binary tree from preorder and inorder” technique. The next preorder key is always the root of the current subtree; its position mid in the inorder splits the remaining keys into the left part [lo, mid - 1] and the right part [mid + 1, hi]. A hash map from key to inorder index makes each split O(1).
The preorder index is shared across all calls (here, a one-element array next), because preorder lists the entire left subtree before the right subtree, which is exactly the order in which the recursion consumes keys.
Algorithm.
- Copy and sort
preorderto getinorder; map each key to its index. buildRange(lo, hi): iflo > hi, returnnull. Otherwise take the next preorder keyv, findmid = pos[v].- Build the left child from
[lo, mid - 1], then the right child from[mid + 1, hi].
static TreeNode better(int[] preorder) {
int[] inorder = preorder.clone();
Arrays.sort(inorder);
Map<Integer, Integer> pos = new HashMap<>();
for (int i = 0; i < inorder.length; i++) pos.put(inorder[i], i);
int[] next = {0}; // index of the next preorder key to place
return buildRange(preorder, pos, next, 0, inorder.length - 1);
}
static TreeNode buildRange(int[] preorder, Map<Integer, Integer> pos, int[] next, int lo, int hi) {
if (lo > hi) return null; // no keys belong to this subtree
int v = preorder[next[0]++];
TreeNode node = new TreeNode(v);
int mid = pos.get(v); // keys left of mid go left, right of mid go right
node.left = buildRange(preorder, pos, next, lo, mid - 1);
node.right = buildRange(preorder, pos, next, mid + 1, hi);
return node;
}
Complexity. Time O(n log n) for the sort; the construction itself is O(n). Space O(n) for the sorted copy and the map, plus O(h) for recursion.
Approach 3: Optimal, consume the preorder with an upper bound
Intuition. The sort and the map exist only to answer one question: “does the next preorder key still belong to the subtree I am building?” We can answer that with a bound instead. While building the left subtree of a node with key v, every key must be below v; the first key above v means the left subtree is complete. While building the right subtree of v, keys must be below whatever bound v itself had to respect (the key of the nearest ancestor whose left subtree contains v).
Why is no lower bound needed? Suppose we are building the right subtree of v and the next key is smaller than v. In a valid preorder that key would have to belong to v’s left subtree, which has already been finished and passed in the sequence. That cannot happen, so checking the upper bound alone is enough.
Algorithm.
- Keep a shared index
nextintopreorder, starting at0. buildWithBound(bound): if all keys are used orpreorder[next] > bound, returnnull.- Otherwise create a node from
preorder[next]and advancenext. - Build its left child with bound
node.valand its right child with the inheritedbound.
static TreeNode optimal(int[] preorder) {
int[] next = {0};
return buildWithBound(preorder, next, Long.MAX_VALUE);
}
static TreeNode buildWithBound(int[] preorder, int[] next, long bound) {
if (next[0] == preorder.length || preorder[next[0]] > bound) return null;
TreeNode node = new TreeNode(preorder[next[0]++]);
node.left = buildWithBound(preorder, next, node.val); // left keys must stay below node.val
node.right = buildWithBound(preorder, next, bound); // right keys inherit the parent's bound
return node;
}
Dry run on [8, 5, 1, 7, 10, 12]:
| call | bound | next key | action |
|---|---|---|---|
| root | +∞ | 8 | create 8 |
| left of 8 | 8 | 5 | create 5 |
| left of 5 | 5 | 1 | create 1 |
| left of 1 | 1 | 7 | 7 > 1, null |
| right of 1 | 5 | 7 | 7 > 5, null |
| right of 5 | 8 | 7 | create 7 |
| left of 7 | 7 | 10 | 10 > 7, null |
| right of 7 | 8 | 10 | 10 > 8, null |
| right of 8 | +∞ | 10 | create 10 |
| left of 10 | 10 | 12 | 12 > 10, null |
| right of 10 | +∞ | 12 | create 12 |
| children of 12 | 12 / +∞ | none left | null, null |
Complexity. Time O(n): every call either creates a node (n times) or returns null immediately (at most n + 1 times, one per empty child slot). Space O(h) for the recursion.
The bound is a long starting at Long.MAX_VALUE, so a key equal to Integer.MAX_VALUE is accepted at the top level. The tests include [0, Integer.MIN_VALUE, Integer.MAX_VALUE].
An equivalent iterative version uses a stack: push each new key; if it is smaller than the stack top it becomes that node’s left child, otherwise pop while the top is smaller and attach it as the right child of the last popped node. It is also O(n) and avoids recursion, but the bound version shows the reasoning more directly.
Edge cases and pitfalls
- Empty array. All three methods return
null. - Sorted input. Increasing or decreasing preorder produces a chain. The brute force becomes quadratic, and the recursive methods recurse
ndeep. The tests include both chains. - Shared index. The preorder index must be shared across recursive calls (field, array cell, or object). Passing a plain
intby value would make every call restart from the same key. - Duplicates. The problem guarantees distinct keys. If duplicates were allowed, you would need a rule for which side equal keys go on, and the insertion method (
>=goes right) and the bound method (>stops the left subtree) would disagree. - Checking the result. Comparing level orders is a quick way to test, because a BST built from a preorder of distinct keys is unique.
Related problems
- Construct a BT from Preorder and Inorder is the general technique used by Approach 2.
- Construct the Binary Tree from Postorder and Inorder Traversal is the mirrored version with the root at the end.
- Insert a given node in BST is the insertion step of Approach 1.
- Check if a tree is a BST or not uses the same “pass a bound down the recursion” idea.
- Serialize and De-serialize BT shows why a BST needs only its preorder to be rebuilt, while a general tree needs null markers.