Skip to content

Coding patternsBlind 75

Construct Binary Tree from Preorder and Inorder Traversal

Preorder names each root first, and inorder splits the rest into the left and right subtree. Solved two ways, searching inorder for each root in O(n²) and a hash map of positions in O(n), with a step-through, code in five languages and the Big O worked out from the code.

You are given two lists of the same values, taken from one binary tree in two different orders:

  • preorder lists each node, then its left subtree, then its right subtree.
  • inorder lists the left subtree, then the node, then the right subtree.

Rebuild the tree and return its root. All values are different, which makes the tree unique.

Trees in the output are written level by level, from left to right. null marks a missing child.

Example 1

Input: preorder = [5, 8, 1, 6, 3, 2], inorder = [1, 8, 5, 3, 6, 2]

Output: [5, 8, 6, 1, null, 3, 2]

Preorder starts with 5, so 5 is the root. In inorder, 1 and 8 are left of 5 and 3, 6, 2 are right of it. The same split repeats inside each side.

581632

Example 2

Input: preorder = [7], inorder = [7]

Output: [7]

One value makes a tree with one node.

Example 3

Input: preorder = [1, 2, 3], inorder = [3, 2, 1]

Output: [1, 2, null, 3]

In inorder, everything sits left of 1, and then left of 2. So the tree leans all the way left.

  • 1 ≤ n ≤ 3,000, where n is the length of both lists.
  • -3,000 ≤ value ≤ 3,000
  • All values are different, and both lists come from the same tree.
Can values repeat?
No. That matters: with repeats, one value could sit in two places in inorder, and the split would be unclear.
Are the two lists always consistent?
Yes. They always come from one real tree, so no error checks are needed.
Is the tree a BST?
No. It can be any binary tree. Only the two orders tell us the shape.
How deep can the tree be?
Up to n = 3,000 levels if it is a chain, so the recursion can get that deep.

Each traversal holds one half of the information. Preorder says which value is the root: it is always listed first. Inorder says where the root splits its part into left and right. Splitting a part and then solving each side the same way is a tree traversal run backwards: recursion that builds the tree instead of reading it.

This problem has two approaches. There is no useful step between them.

Optimal O(n)

Keep everything from the brute force, and fix the slow step. Before building, walk inorder once and store each value’s position in a hash map: a table that finds the entry for a key in one step. The values are all different, so each one has exactly one position.

Now finding the root of a part is one lookup, position_of[value], instead of a search.

Why it is correct: preorder always gives the root of the part being built next, and the root’s position in inorder splits that part exactly into its left and right subtree. The map changes how fast we find that position, not which position we find.

preorder
508112633425
preorder_index
inorder
108152336425
value185362
index012345
  1. position_of = {value: i for i, value in enumerate(inorder)}
  2. preorder_index = 0
  3. def build(start, end): # inorder[start..end]
  4. if start > end: return None
  5. node = TreeNode(preorder[preorder_index]); preorder_index += 1
  6. root_index = position_of[node.val]
  7. node.left = build(start, root_index - 1)
  8. node.right = build(root_index + 1, end)
  9. return node
  10. return build(0, len(inorder) - 1)

First store where each value sits in inorder: 1 at 0, 8 at 1, 5 at 2, 3 at 3, 6 at 4, 2 at 5. Later each lookup is one step.

1 / 8
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
private int[] preorder;
private int preorderIndex = 0;
private Map<Integer, Integer> positionOf = new HashMap<>();
// Hash map: remember where each value sits in inorder, so splitting a
// part takes one lookup instead of a search. One call per node, so n steps.
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder;
// Values are all different, so each one has exactly one position.
for (int i = 0; i < inorder.length; i++) positionOf.put(inorder[i], i);
return build(0, inorder.length - 1);
}
// Builds the tree whose values are inorder[start..end].
private TreeNode build(int start, int end) {
if (start > end) return null;
// Preorder lists a root before its subtrees, so the next
// unused preorder value is the root of this part.
TreeNode node = new TreeNode(preorder[preorderIndex++]);
// Left of the root in inorder is the left subtree.
int rootIndex = positionOf.get(node.val);
// Build the left side first, because preorder lists it first.
node.left = build(start, rootIndex - 1);
node.right = build(rootIndex + 1, end);
return node;
}
}
Time O(n)Space O(n)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    There are two parts. Filling the map does one step per value. Then each call of build does a fixed job: one check, one new node, one lookup and at most two more calls. So each map entry and each call counts as 1 step.

    1. position_of = {value: i for i, value in enumerate(inorder)}
    2. preorder_index = 0
    3. def build(start, end): # inorder[start..end]
    4. if start > end: return None
    5. node = TreeNode(preorder[preorder_index]); preorder_index += 1
    6. root_index = position_of[node.val]
    7. node.left = build(start, root_index - 1)
    8. node.right = build(root_index + 1, end)
    9. return node
    10. return build(0, len(inorder) - 1)
  2. 2 Count the runs on Example 1

    Filling the map takes 6 steps, one per value. Then build is called once for each of the 6 values, and once for each empty part. Every node has two children spots, 12 in all, and 5 of them hold a node, so 7 are empty.

    6 + 6 + 7 = 19 steps.

    partfill the mapcalls with a nodecalls on an empty parttotal
    steps66719
  3. 3 Write the count for any n

    The map takes n steps. A tree of n nodes has n + 1 empty child spots, and each node and each empty spot gets one call:

    n + n + (n + 1) = 3n + 1 steps

    Check it with Example 1: 3 × 6 + 1 = 19.

  4. 4 Keep only the part that grows fastest

    • Drop the + 1. A fixed extra step does not change the growth.
    • Drop the 3. When n doubles, 3n doubles too.

    What is left is n.

    So the time is O(n).

  5. S Space: count the extra memory the same way

    The map holds n entries. The call stack holds up to h calls, and h is at most n. Both grow with n, so the space is O(n).

    position_of (n entries)preorder_indexcall stack (h frames)

2,000 random trees of 1 to 10 nodes, in every shape. From their preorder and inorder lists, the brute force and the optimal solution both rebuilt the exact tree every time.

Largest inputsResult
3,000 values (the most allowed), a balanced tree. Optimal.0.001 s, and about 0.5 MB of extra memory: the new tree and the map of positions, which is the O(n) space.
3,000 values in one long line. Optimal.0.002 s, and about 1 MB of extra memory: the same, plus 3,000 calls that wait at once.
3,000 values in one long line to the left. Brute force.0.14 s. Each search for the root walks along the inorder list, about 4.5 million steps in total. The map makes each search one step.