105. Construct Binary Tree From Preorder And Inorder Traversal - Explanation

Problem Link

Description

You are given two integer arrays preorder and inorder.

  • preorder is the preorder traversal of a binary tree
  • inorder is the inorder traversal of the same tree
  • Both arrays are of the same size and consist of unique values.

Rebuild the binary tree from the preorder and inorder traversals and return its root.

Example 1:

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

Output: [1,2,3,null,null,null,4]

Example 2:

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

Output: [1]

Constraints:

  • 1 <= inorder.length <= 1000.
  • inorder.length == preorder.length
  • -1000 <= preorder[i], inorder[i] <= 1000


Topics

Recommended Time & Space Complexity

You should aim for a solution with O(n) time and O(n) space, where n is the number of nodes.


Hint 1

You can observe that the in-order traversal helps divide the array into two halves based on the root node: the left part corresponds to the left subtree, and the right part corresponds to the right subtree. Can you think of how we can get the index of the root node in the in-order array? Maybe you should look into the pre-order traversal array.


Hint 2

From the pre-order traversal, we know that the first node is the root node. Using this information, can you now construct the binary tree?


Hint 3

After getting the root node from pre-order traversal, we then look for the index of that node in the in-order array. We can linearly search for the index but this would be an O(n^2) solution. Can you think of a better way? Maybe we can use a data structure to get the index of a node in O(1)?


Hint 4

We can use a hash map to get the index of any node in the in-order array in O(1) time. How can we implement this?


Hint 5

We use Depth First Search (DFS) to construct the tree. A global variable tracks the current index in the pre-order array. Indices l and r represent the segment in the in-order array for the current subtree. For each node in the pre-order array, we create a node, find its index in the in-order array using the hash map, and recursively build the left and right subtrees by splitting the range [l, r] into two parts for the left and right subtrees.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Binary Tree Structure - Understanding how nodes connect via left and right children
  • Tree Traversal Orders - Knowing what preorder (root-left-right) and inorder (left-root-right) sequences represent
  • Recursion / DFS - Building solutions by breaking problems into subproblems on left and right subtrees
  • Hash Maps - Using dictionaries for O(1) index lookups to optimize from O(n^2) to O(n)

Intuition

The first element of the preorder array is always the root. We can find this root's position in the inorder array, which divides inorder into left and right subtrees. Elements before the root in inorder belong to the left subtree, and elements after belong to the right subtree. The same split applies to preorder. We recursively build left and right subtrees using the corresponding portions of both arrays.

Algorithm

  1. If either array is empty, return null (base case).
  2. Create a root node with the first element of preorder.
  3. Find the index of the root value in inorder (call it mid).
  4. Recursively build the left subtree using preorder[1:mid+1] and inorder[0:mid].
  5. Recursively build the right subtree using preorder[mid+1:] and inorder[mid+1:].
  6. Return the root node.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        if not preorder or not inorder:
            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 root

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(n)O(n)

Intuition

In the basic DFS approach, we search for the root's position in inorder using linear search, which takes O(n) time per node. By precomputing a hash map from values to their indices in inorder, we can find the root's position in O(1) time. We also avoid creating new arrays by passing indices that define the current subarray boundaries.

Algorithm

  1. Build a hash map mapping each value in inorder to its index.
  2. Maintain a global preorder index starting at 0.
  3. Define a recursive function dfs(l, r) for the inorder range [l, r].
  4. If l > r, return null (base case).
  5. Get the root value from preorder at the current index, increment the index.
  6. Look up the root's position in the hash map (call it mid).
  7. Recursively build left subtree with dfs(l, mid-1) and right subtree with dfs(mid+1, r).
  8. Return the root node.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        indices = {val: idx for idx, val in enumerate(inorder)}

        self.pre_idx = 0
        def dfs(l, r):
            if l > r:
                return None

            root_val = preorder[self.pre_idx]
            self.pre_idx += 1
            root = TreeNode(root_val)
            mid = indices[root_val]
            root.left = dfs(l, mid - 1)
            root.right = dfs(mid + 1, r)
            return root

        return dfs(0, len(inorder) - 1)

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

3. Depth First Search (Optimal)

Intuition

We can avoid the hash map entirely by using a limit-based approach. Instead of explicitly finding the root's position, we pass a "limit" value that tells us when to stop building the left subtree. When we encounter the limit value in inorder, we know the left subtree is complete. The preorder index tells us which node to create next, and the inorder index tells us when we have finished a subtree.

Algorithm

  1. Maintain two global indices: preIdx for preorder and inIdx for inorder.
  2. Define a recursive function dfs(limit) that builds a subtree until it hits the limit value.
  3. If preIdx >= n, return null (no more nodes).
  4. If inorder[inIdx] == limit, increment inIdx and return null (subtree complete).
  5. Create a root node with preorder[preIdx], increment preIdx.
  6. Build the left subtree with dfs(root.val) since nodes less than root appear before it in inorder.
  7. Build the right subtree with dfs(limit) using the original limit.
  8. Return the root node. Start with dfs(infinity) or a value larger than any node.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        preIdx = inIdx = 0
        def dfs(limit):
            nonlocal preIdx, inIdx
            if preIdx >= len(preorder):
                return None
            if inorder[inIdx] == limit:
                inIdx += 1
                return None

            root = TreeNode(preorder[preIdx])
            preIdx += 1
            root.left = dfs(root.val)
            root.right = dfs(limit)
            return root
        return dfs(float('inf'))

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n) for recursion stack.

4. Morris Traversal

Intuition

Morris traversal allows us to build the tree iteratively without using a recursion stack. The idea is to use the right pointers of nodes to temporarily store parent references, simulating the call stack. We build nodes as we iterate through preorder, connecting them via left/right pointers. When we finish a left subtree (detected by matching the inorder sequence), we restore the original structure by clearing temporary links and moving up the tree.

Algorithm

  1. Create a dummy head node and set curr to point to it.
  2. Iterate through preorder with index i and inorder with index j.
  3. Create a new node for preorder[i] and attach it as curr's right child, then move curr to this new node.
  4. While preorder[i] does not match inorder[j], keep creating left children (storing parent in right pointer).
  5. When a match is found, increment j. While curr.right exists and matches inorder[j], clear the temporary right link and move up.
  6. Continue until all nodes are processed.
  7. Return head.right as the actual root.
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        head = TreeNode(None)
        curr = head
        i, j, n = 0, 0, len(preorder)
        while i < n and j < n:
            # Go right and then as far left as possible
            curr.right = TreeNode(preorder[i], right = curr.right)
            curr = curr.right
            i += 1
            while i < n and curr.val != inorder[j]:
                curr.left = TreeNode(preorder[i], right=curr)
                curr = curr.left
                i += 1
            j += 1
            while curr.right and j < n and curr.right.val == inorder[j]:
                prev = curr.right
                curr.right = None
                curr = prev
                j += 1
        
        return head.right

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity:
    • O(1)O(1) extra space.
    • O(n)O(n) for the output tree.

Common Pitfalls

Off-by-One Error When Splitting Arrays

The split point mid represents the root's index in inorder. When slicing preorder, the left subtree uses preorder[1:mid+1] (not preorder[1:mid]), since there are exactly mid elements in the left subtree.

# Wrong: preorder[1:mid]
# Correct: preorder[1:mid+1]

Building Right Subtree Before Left Subtree

When using a global preorder index, the left subtree must be built first. Preorder visits root, then left, then right. Building right first consumes wrong nodes from the preorder array.

Confusing Preorder and Inorder Roles

The root always comes from preorder (first element), while the split point is found in inorder. Swapping these roles produces an incorrect tree structure.