Construct a binary tree using arrays of values obtained after a preorder traversal and an inorder traversal of the tree.
Input: preorder = [5, 9, 2, 3, 4, 7], inorder = [2, 9, 5, 4, 3, 7]
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
Preorder is a photo that always shows the boss first. Inorder is a photo where the boss stands in the middle, with their left-side people to the left and right-side people to the right. Combine the two: preorder tells you who the boss is; inorder tells you who is on the boss’s left versus right. Repeat for each group.
The first question that might come to mind is why both the preorder and inorder arrays are needed to solve this problem. Can’t we just use one of them? The reason is that each individual traversal order could represent multiple possible trees. For example, below are some trees that correspond to the following inorder traversal:
If one traversal array isn’t enough, it might be possible to build the tree using an additional traversal array as a reference point, to help identify how each node should be placed.
The root node
Whatever our approach, we initially need a way to access the root node since the root node is necessary to construct the rest of the tree.
To identify where the root node is, let’s first remind ourselves how nodes are processed during each traversal algorithm.
| Preorder traversal | Inorder traversal |
|---|---|
| 1. Process the current node | 1. Process the left subtree |
| 2. Process the left subtree | 2. Process the current node |
| 3. Process the right subtree | 3. Process the right subtree |
Notice that during preorder traversal, the current node is processed before its subtrees. From this, we can infer that preorder traversal processes the root node first, meaning the first value in the preorder array is the value of the root node.
With this established, let’s find a method to build the rest of the tree.
Building the tree
Consider the preorder and inorder traversal arrays below and the corresponding binary tree:
As mentioned above, we know the first value of the preorder array, 5, is the value of the root node.
Now the question is, what node should be placed next? The preorder array alone isn’t enough to determine this. So, we should consult the inorder array.
Inorder traversal first visits a node’s left subtree before processing the node itself, so we can deduce that all values in the inorder array to the left of 5 are part of its left subtree. Similarly, all values to the right of 5 in the inorder array belong to its right subtree:
Now what? With the root node placed, we have two subtrees to build: node 5’s left and right subtrees.
If we build the tree starting with the already-created root node, followed by the left subtree and then the right subtree, we’re effectively building the tree using preorder traversal. This is useful because we happen to have an array of values from a preorder traversal, which means we know the exact order in which the nodes should be created.
As we’re building the tree using preorder traversal, the next node to be created at any point will be the next value in the preorder array. This means we can iterate through the preorder array to place each new node.
Based on this, we know the next node to be placed will have a value of 9. But how do we know if node 9 is a left child or a right child of 5?
This is where the inorder array comes in, as it helps us determine the structure of the tree. Consider the left subtree:
When we want to build the left subtree of a node, we look at the part of the inorder array that corresponds to the left subtree. In our example, this is the subarray [2, 9].
If this subarray is not empty, we proceed to build the left subtree, starting with node 9.
If this subarray is empty, it means there is no left subtree, so the current node’s left child is null.
The same logic applies to the right subtree: we only build it if its corresponding inorder subarray isn’t empty.
Now, let’s devise a strategy for our algorithm. To utilize the two traversal arrays, we can assign a pointer to each:
A preorder_index points to the value of the current subtree’s root node. Once this node is created, increment preorder_index so it points to the value of the next node to be created.
An inorder_index is used to determine the position of the same value in the inorder array. We can find the index of this value by searching through the inorder array.
At each recursive call, once these indexes are obtained, we:
Create the current node using the value pointed at by preorder_index.
Increment preorder_index so it points to the value of the next node to be created.
Make a recursive call to build the current node’s left and right subtrees:
inorder[0, inorder_index - 1] to build the left subtree.inorder[inorder_index + 1, n - 1] to build the right subtree.Optimization - left and right pointers
One inefficiency of the above approach is that we extract entire subarrays out of the inorder array whenever we make a recursive call, which takes time each time we do this, where denotes the length of each input array. A more efficient approach is to define the range of each inorder subarray using left and right pointers. Specifically:
The left subtree contains the values in the range [left, inorder_index - 1].
The right subtree contains the values in the range [inorder_index + 1, right].
This allows us to define subarrays by just moving pointers, as opposed to creating completely new subarrays.
Optimization - hash map
At each node, it’s necessary to set inorder_index to the position of the same value pointed at by preorder_index. Performing a linear search for this value would take time for each node. Instead, we can use a hash map to store the inorder array values and their indexes, allowing us to retrieve any value's index from the inorder array in time.
from ds import TreeNode
from typing import List
preorder_index = 0
inorder_indexes_map = {}
def build_binary_tree(preorder: List[int], inorder: List[int]) -> TreeNode:
global inorder_indexes_map
# Populate the hash map with the inorder values and their indexes.
for i, val in enumerate(inorder):
inorder_indexes_map[val] = i
# Build the tree and return its root node.
return build_subtree(0, len(inorder) - 1, preorder, inorder)
def build_subtree(left, right, preorder, inorder):
global preorder_index, inorder_indexes_map
# Base case: if no elements are in this range, return None.
if left > right:
return None
val = preorder[preorder_index]
# Set 'inorder_index' to the index of the same value pointed at by
# 'preorder_index'.
inorder_index = inorder_indexes_map[val]
node = TreeNode(val)
# Advance 'preorder_index' so it points to the value of the next node to be
# created.
preorder_index += 1
# Build the left and right subtrees and connect them to the current node.
node.left = build_subtree(left, inorder_index - 1, preorder, inorder)
node.right = build_subtree(inorder_index + 1, right, preorder, inorder)
return nodeint g_pre;
unordered_map<int,int> g_inpos;
TreeNode* buildRec(vector<int>& pre, int lo, int hi) {
if (lo > hi) {
return nullptr;
}
int val = pre[g_pre]; // next preorder value = subtree root
g_pre++;
TreeNode* node = new TreeNode(val);
int mid = g_inpos[val]; // where the root sits in inorder
node->left = buildRec(pre, lo, mid - 1);
node->right = buildRec(pre, mid + 1, hi);
return node;
}
TreeNode* buildBinaryTree(vector<int>& pre, vector<int>& ino) {
g_inpos.clear();
for (int i = 0; i < (int)ino.size(); i++) {
g_inpos[ino[i]] = i;
}
g_pre = 0;
return buildRec(pre, 0, (int)ino.size() - 1);
}int gPre;
Map<Integer,Integer> gInpos;
TreeNode buildRec(int[] pre, int lo, int hi) {
if (lo > hi) return null;
int val = pre[gPre++];
TreeNode node = new TreeNode(val);
int mid = gInpos.get(val);
node.left = buildRec(pre, lo, mid - 1);
node.right = buildRec(pre, mid + 1, hi);
return node;
}
TreeNode buildBinaryTree(int[] pre, int[] ino) {
gInpos = new HashMap<>();
for (int i = 0; i < ino.length; i++) gInpos.put(ino[i], i);
gPre = 0;
return buildRec(pre, 0, ino.length - 1);
}Time complexity: The time complexity of build_binary_tree is , as it makes one call to the build_subtree function, which recursively traverses each element in the preorder and inorder arrays once, resulting in an runtime.
Space complexity: The space complexity is due to the space taken up by the recursive call stack, which can grow as large as the height of the binary tree. The largest possible height of a binary tree is . The hash map inorder_indexes_map also takes up space.
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
Root is preorder’s first element; split inorder around it:
| preorder ptr | root | inorder range | builds |
|---|---|---|---|
| 0 → 3 | 3 | [9,3,15,20,7] | left=[9], right=[15,20,7] |
| 1 → 9 | 9 | [9] | leaf |
| 2 → 20 | 20 | [15,20,7] | left=[15], right=[7] |
| 3 → 15 | 15 | [15] | leaf |
| 4 → 7 | 7 | [7] | leaf |
Notice the preorder pointer marches 3, 9, 20, 15, 7 — each becomes a root exactly when its subtree is being built. ✓
| preorder ptr | root | inorder range | left / right |
|---|---|---|---|
| 0 → 1 | 1 | [3,2,1] | left=[3,2], right=[] (empty) |
| 1 → 2 | 2 | [3,2] | left=[3], right=[] (empty) |
| 2 → 3 | 3 | [3] | leaf |
The root sits at the far right of inorder each time, so the right side is always empty (left > right → None) and the tree leans all the way left. ✓
state = {'pre': 0} is a tiny mutable box so the nested build can advance the shared pointer. The dict comprehension builds the O(1) index map.
A file-scope g_pre and g_inpos hold the shared state, reset at the start of buildBinaryTree. unordered_map gives O(1) lookup.
Instance fields gPre and gInpos play the same role, reset each call. HashMap gives O(1) root lookup. Logic matches the other two exactly.