Appearance
4.14.13 — Construct Binary Tree from Preorder and Inorder Traversal
LeetCode 105 · Medium · ★ Blind 75
The problem
Rebuild the tree from its pre-order and in-order traversals. All values are distinct.
preorder = [3, 9, 20, 15, 7]
inorder = [9, 3, 15, 20, 7]
3
/ \
9 20
/ \
15 7
15 7To put this in plain English: you are given two arrays. preorder is the list of node values obtained by visiting a tree in Root \rightarrow Left \rightarrow Right order. inorder is the list of node values obtained by visiting it in Left \rightarrow Root \rightarrow Right order. You must physically reconstruct the original tree and return its root.
Constraints to Keep in Mind:
1 <= preorder.length <= 3000inorder.length == preorder.length-3000 <= preorder[i], inorder[i] <= 3000preorderandinorderconsist of unique values.- Each value of
inorderalso appears inpreorder. preorderis guaranteed to be the preorder traversal of the tree.inorderis guaranteed to be the inorder traversal of the tree.
The pattern
Two facts, one from each traversal.
Pre-order visits the root first. So preorder[0] is the root of the whole tree. That is the only thing pre-order tells you directly, and it is enough.
In-order visits the left subtree, then the root, then the right subtree. So once you know which value is the root, finding it in inorder splits the array cleanly: everything to its left belongs to the left subtree, everything to its right belongs to the right subtree.
Put them together:
preorder = [3, 9, 20, 15, 7]
↑ root
inorder = [9, 3, 15, 20, 7]
└┘ ↑ └──────┘
left root rightNow you know the left subtree has 1 node and the right subtree has 3. And because pre-order lists the root, then all of the left subtree, then all of the right subtree, that size splits preorder too:
preorder = [3 | 9 | 20, 15, 7]
root left rightTwo subproblems of the same shape. Recurse.
The size from inorder is what tells you where to cut preorder. That is the one connection to hold on to.
Why two traversals are needed
A single traversal does not determine a tree. Both of these give pre-order [1, 2]:
1 1
/ \
2 2Pre-order alone tells you the root but not where the left subtree ends. In-order alone tells you the split but not which value is the root. Together they are enough.
Pre-order plus post-order is not enough for a general binary tree, because neither says where the left subtree stops when a node has only one child. It works only if every node has 0 or 2 children.
The naive solution
python
def buildTree(self, preorder, inorder):
if not preorder:
return None
root = TreeNode(preorder[0])
mid = inorder.index(preorder[0])
root.left = self.buildTree(preorder[1:mid+1], inorder[:mid])
root.right = self.buildTree(preorder[mid+1:], inorder[mid+1:])
return rootCorrect, and pleasantly short. Two things make it slow:
inorder.index(...)is a linear scan, done once per node → O(n^2).- The slices copy arrays at every level → another O(n^2) and heavy allocation.
The linear solution
Fix both problems. Replace the scan with a hash map from value to its index in inorder, and replace the slices with index ranges.
python
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
position = {val: i for i, val in enumerate(inorder)} # value → index
self.pre = 0 # next root to place
def build(left: int, right: int):
if left > right:
return None
value = preorder[self.pre]
self.pre += 1
node = TreeNode(value)
mid = position[value]
node.left = build(left, mid - 1) # must be built FIRST
node.right = build(mid + 1, right)
return node
return build(0, len(inorder) - 1)ts
function buildTree(preorder: number[], inorder: number[]): TreeNode | null {
const position = new Map<number, number>();
inorder.forEach((v, i) => position.set(v, i));
let pre = 0;
function build(left: number, right: number): TreeNode | null {
if (left > right) return null;
const value = preorder[pre++];
const node = new TreeNode(value);
const mid = position.get(value)!;
node.left = build(left, mid - 1);
node.right = build(mid + 1, right);
return node;
}
return build(0, inorder.length - 1);
}Code Walkthrough
position = {val: i for i, val in enumerate(inorder)}: Create a hash map so we can look up the index of any value in theinorderarray in O(1) time. This is what makes the algorithm linear.self.pre = 0: This is a global/instance pointer that tells us which element ofpreorderto process next. Becausepreorderalways gives roots first, we can just consume it from left to right.def build(left: int, right: int):: Our recursive helper takes the inclusive boundaries of theinorderarray that belong to the current subtree.if left > right: return None: Base case. The range is empty, so there is no node here.value = preorder[self.pre]: Grab the root value for this subtree frompreorder.self.pre += 1: Advance the pointer so the next recursive call gets the next root.node = TreeNode(value): Create the actual root node object.mid = position[value]: Ask the hash map where this root value sits in theinorderarray. Everything to the left ofmid(fromlefttomid - 1) belongs to the left subtree. Everything to the right (frommid + 1toright) belongs to the right subtree.node.left = build(left, mid - 1): Recursively build the left child FIRST. It must be first becausepreorderlists all left-subtree roots before right-subtree roots.node.right = build(mid + 1, right): Recursively build the right child.return node: Return the constructed subtree back to its parent.return build(0, len(inorder) - 1): Kick off the recursion with the fullinorderrange.
Hand Trace
preorder = [3, 9, 20, 15, 7], inorder = [9, 3, 15, 20, 7]position = {9:0, 3:1, 15:2, 20:3, 7:4}, pre = 0
build(0, 4):value = preorder[0] = 3.prebecomes 1.mid = position[3] = 1.node= 3.node.left=build(0, 0)node.right=build(2, 4)
build(0, 0)(left of 3):value = preorder[1] = 9.prebecomes 2.mid = position[9] = 0.node= 9.node.left=build(0, -1)\rightarrow returnsNone.node.right=build(1, 0)\rightarrow returnsNone.- Returns node 9.
3.left = 9.
build(2, 4)(right of 3):value = preorder[2] = 20.prebecomes 3.mid = position[20] = 3.node= 20.node.left=build(2, 2)node.right=build(4, 4)
build(2, 2)(left of 20):value = preorder[3] = 15.prebecomes 4.mid = position[15] = 2.node= 15. Returns node 15.20.left = 15.
build(4, 4)(right of 20):value = preorder[4] = 7.prebecomes 5.mid = position[7] = 4.node= 7. Returns node 7.20.right = 7.
Tree fully built! ✓
The single moving pointer into preorder is the trick that removes the arithmetic. Instead of computing which slice of preorder belongs to each subtree, just consume it in order. Pre-order is defined as root, then the whole left subtree, then the whole right subtree — which is exactly the order the recursion asks for values.
So the left call must come before the right call. Swap those two lines and the pointer hands the right subtree values that belong to the left one, and the tree comes out wrong with no error message. This is the only line in the solution that can silently break.
left > right means an empty range, which is the base case.
Complexity
- Time Complexity: O(n). We iterate through the n elements of
inorderonce to build the hash map (O(n)). Then we make exactly onebuildcall for each of the n elements inpreorder. Each call takes O(1) time to do hash map lookups, array indexing, and object creation. O(n) + O(n) = O(n). - Space Complexity: O(n) extra space. The hash map takes O(n) space to store n key-value pairs. The recursion stack uses O(h) space, where h is the height of the tree. Since h \le n, the dominant term is O(n).
Where this goes next
- Construct from Inorder and Postorder (LeetCode 106) — post-order visits the root last, so consume
postorderfrom the back and build the right subtree first. A neat mirror image, and a good check that you understood why the order matters here. - Construct from Preorder and Postorder — only possible when every node has 0 or 2 children.
- Serialize and Deserialize — a single pre-order traversal is enough if you write markers for the empty children, which is the whole idea of 4.14.15.
The rule: pre-order gives you the root, in-order gives you the split, and the size of the split tells you where to cut the other array.
What the interviewer will push on
"Why are two traversals needed?" Give the two-node counterexample.
"Why is the naive version O(n^2)?" The linear index search plus the array copying.
"Why must the left call come before the right?" The preorder pointer only moves forward, and pre-order lists the whole left subtree before the right.
"What about preorder and postorder?" Ambiguous unless every node has 0 or 2 children.
"What changes for inorder and postorder?" Consume from the back and build right first.
One thing to volunteer: state the two facts before writing code — "pre-order gives the root, in-order gives the split" — and then say that the split size is what cuts the pre-order array. That is the derivation, and it takes fifteen seconds.
Next: 4.14.14 Binary Tree Maximum Path Sum — the return-one-record-another structure from 4.14.3, made harder by negative numbers.