Problem Walkthrough

Construct Binary Tree LeetCode 105 Guide

LeetCode 105 Construct Binary Tree from Preorder and Inorder Traversal exploits a fundamental property: the first element of preorder is always the root, and finding that root in the inorder array divides it into left and right subtrees. A precomputed hashmap gives O(1) index lookup into inorder, reducing the total complexity from O(n^2) naive to O(n). Passing index boundaries instead of slicing arrays keeps space usage to O(n) for the hashmap and recursion stack.

9 min read|

Construct Binary Tree

LeetCode 105

Problem Overview

LeetCode 105, Construct Binary Tree from Preorder and Inorder Traversal, gives you two integer arrays: preorder and inorder, each containing the same n unique values representing the nodes of a binary tree. Your task is to reconstruct the binary tree and return its root.

Preorder traversal visits nodes in root-left-right order. Inorder traversal visits them in left-root-right order. Given both traversals, there is exactly one binary tree that produces them — because all values are unique, the traversals together uniquely identify the structure. This problem appears frequently at Google, Facebook, and Amazon.

The problem is medium difficulty and is the foundation for LeetCode 106, which reconstructs a binary tree from postorder and inorder. Mastering LeetCode 105 gives you the mental model for an entire family of tree reconstruction problems.

  • Input: preorder and inorder integer arrays of length n
  • Output: root of the reconstructed binary tree
  • Constraints: 1 <= n <= 3000, -3000 <= values <= 3000
  • All values in preorder and inorder are unique
  • inorder is a permutation of preorder
  • Example: preorder=[3,9,20,15,7], inorder=[9,3,15,20,7] → tree with root 3

The Key Insight

The core observation is that preorder[0] is always the root of the current subtree. Once you know the root, find its position in the inorder array. Everything to the left of that position in inorder belongs to the left subtree; everything to the right belongs to the right subtree.

The size of the left subtree in inorder tells you how many elements to take from preorder for the left subtree: the next leftSize elements in preorder after the root form the preorder traversal of the left subtree. The remaining elements form the preorder traversal of the right subtree.

This two-array partnership uniquely defines the tree because all values are unique. With duplicates, the reconstruction would be ambiguous — you could not determine which occurrence of a repeated value in inorder corresponds to the root.

💡

Preorder Gives the Root, Inorder Tells You the Split

Preorder gives you the root (always the first element of the current subarray). Inorder tells you the split (the root's index in inorder separates left and right subtrees). This two-array partnership uniquely defines the tree structure — commit this pattern to memory and the rest of the algorithm follows naturally.

Recursive Construction

The recursive approach pops the first element of the remaining preorder array to use as the current root. It then locates this value in the inorder array to determine the left subtree size. The left subtree is built from the next leftSize elements of preorder and the left portion of inorder. The right subtree is built from the remaining preorder elements and the right portion of inorder.

Each recursive call reduces the problem to a smaller subtree. The base case is when the preorder or inorder subarray is empty, in which case null is returned. The recursion terminates because each call processes at least one node and the subarrays shrink with every level.

Without any optimization, the naive approach slices arrays at each recursive call, which is O(n) per call for the slice operation, resulting in O(n^2) total time for a balanced tree and O(n^2) worst case for a skewed tree.

  1. 1If preorder is empty, return null
  2. 2rootVal = preorder[0]; create TreeNode(rootVal)
  3. 3Find rootVal in inorder array; let idx = inorder.index(rootVal)
  4. 4leftSize = idx (number of elements to the left of root in inorder)
  5. 5Recursively build left: buildTree(preorder[1:1+leftSize], inorder[:idx])
  6. 6Recursively build right: buildTree(preorder[1+leftSize:], inorder[idx+1:])
  7. 7Return root node

HashMap Optimization

Searching the inorder array for the root value at each recursive call costs O(n) per call. For a balanced tree with O(log n) levels, this yields O(n log n) total — but for a skewed tree (like a linked list), every call searches an array of size proportional to the remaining nodes, giving O(n^2) total.

The fix is to precompute a hashmap from value to inorder index before any recursion begins. Building the map is O(n) and each subsequent lookup is O(1). With this optimization, the total time complexity is O(n) because each of the n nodes is processed exactly once in O(1) work per node.

The space complexity is O(n) for the hashmap plus O(n) for the recursion call stack in the worst case (skewed tree). For a balanced tree the stack depth is O(log n), but O(n) is the correct worst-case bound.

ℹ️

HashMap Reduces Total Time from O(n²) to O(n)

Without the hashmap, each recursive call searches the inorder array linearly — O(n) per call — making the total O(n²) for skewed trees. Precomputing a hashmap of value→index reduces each lookup to O(1), bringing the total time to O(n). This is the optimization that takes the solution from acceptable to optimal.

Index-Based Implementation

Slicing arrays at each recursive call is expensive in both time (O(n) per slice) and space (creates new arrays). The optimal approach avoids slicing entirely by passing index boundaries instead: inorderStart and inorderEnd define the current inorder window, and a global preorderIndex pointer advances as each node is consumed.

The preorderIndex starts at 0 and increments by 1 each time a node is created — because preorder is root-left-right, consuming nodes left to right in preorder naturally gives the correct root for each subtree. This pointer is shared across all recursive calls (use a list or instance variable in Python, or a class field in Java).

The inorder boundaries are derived from the parent call: after finding the root at inorderMid, the left child gets [inorderStart, inorderMid-1] and the right child gets [inorderMid+1, inorderEnd]. The base case is when inorderStart > inorderEnd.

  1. 1Build inorderMap: {value: index} for all values in inorder
  2. 2Initialize preorderIndex = 0 (global/shared pointer)
  3. 3Call helper(inorderStart=0, inorderEnd=n-1)
  4. 4Base case: if inorderStart > inorderEnd, return null
  5. 5rootVal = preorder[preorderIndex]; preorderIndex++
  6. 6inorderMid = inorderMap[rootVal]
  7. 7root.left = helper(inorderStart, inorderMid - 1)
  8. 8root.right = helper(inorderMid + 1, inorderEnd)
  9. 9Return root

Code Walkthrough — Python and Java

Python with hashmap and index pointer: def buildTree(preorder, inorder): inorderMap = {val: i for i, val in enumerate(inorder)}; preIdx = [0]; def helper(left, right): if left > right: return None; rootVal = preorder[preIdx[0]]; preIdx[0] += 1; root = TreeNode(rootVal); mid = inorderMap[rootVal]; root.left = helper(left, mid - 1); root.right = helper(mid + 1, right); return root; return helper(0, len(inorder) - 1). Time O(n), space O(n).

Java with hashmap and index pointer: class Solution { int preIdx = 0; Map<Integer,Integer> inorderMap = new HashMap<>(); public TreeNode buildTree(int[] preorder, int[] inorder) { for (int i = 0; i < inorder.length; i++) inorderMap.put(inorder[i], i); return helper(preorder, 0, inorder.length - 1); } TreeNode helper(int[] preorder, int left, int right) { if (left > right) return null; int rootVal = preorder[preIdx++]; TreeNode root = new TreeNode(rootVal); int mid = inorderMap.get(rootVal); root.left = helper(preorder, left, mid - 1); root.right = helper(preorder, mid + 1, right); return root; } }.

Both solutions build the inorder hashmap in O(n), then recursively construct the tree in O(n) total with O(1) per node. The preorderIndex advances globally in root-left-right order, which matches preorder traversal exactly. The recursion stack depth is O(n) worst case (skewed tree) or O(log n) for balanced trees.

⚠️

Build the Left Subtree Before the Right

Always recurse into the left subtree before the right. Preorder traversal is root-left-right, so after consuming the root from preorder, the very next elements belong to the left subtree. If you build the right subtree first, the preorderIndex will consume left-subtree nodes for the right subtree, producing a completely wrong tree. Left before right is mandatory.

Ready to master algorithm patterns?

YeetCode flashcards help you build pattern recognition through active recall and spaced repetition.

Start practicing now