← Back to DevBytes

Solving Construct Binary Tree from Preorder and Inorder in Python: Step-by-Step Guide

Introduction to Constructing a Binary Tree from Preorder and Inorder Traversals

Reconstructing a binary tree from its traversal outputs is a classic problem that appears frequently in coding interviews and competitive programming. Among the many variations, Construct Binary Tree from Preorder and Inorder Traversal is one of the most instructive because it forces you to deeply understand how trees are structured and how different traversal strategies encode structural information.

In this tutorial, you will learn what the problem is, why it matters, the underlying theory that makes the reconstruction possible, and how to implement an efficient recursive solution in Python. We will also discuss complexity analysis, edge cases, and best practices to keep your code clean and performant.

What Is the Problem?

Given two lists — a preorder traversal and an inorder traversal of a binary tree — your task is to rebuild the original tree and return its root. Each traversal is a list of node values, and you may assume all values are unique (this assumption is essential for an unambiguous reconstruction).

To recall the definitions:

Because preorder always places the root first, the first element of the preorder list tells you the root of the (sub)tree. Because inorder places the root between its left and right subtrees, locating that root value in the inorder list tells you exactly how many nodes belong to the left subtree versus the right subtree.

A Concrete Example

Suppose you are given:

preorder = [3, 9, 20, 15, 7]
inorder  = [9, 3, 15, 20, 7]

The first element of preorder, 3, is the root. In inorder, 3 sits at index 1, so:

From the preorder list, the next 1 element ([9]) belongs to the left subtree, and the following 3 elements ([20, 15, 7]) belong to the right subtree. Recursively applying the same logic reconstructs the entire tree:

        3
       / \
      9  20
         / \
        15  7

Why This Problem Matters

This problem is more than an interview exercise. It teaches several foundational concepts that real-world developers encounter:

How to Solve It: Step by Step

Step 1: Define the Tree Node

Most platforms (like LeetCode) provide a TreeNode class. If you are writing the solution from scratch, define it yourself:

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

Step 2: Build a Value-to-Index Map for Inorder

Searching for the root value in the inorder list on every recursive call would cost O(n) per lookup, leading to O(n²) overall. Instead, precompute a dictionary that maps each value to its index in inorder. This reduces each lookup to O(1).

inorder_index_map = {val: idx for idx, val in enumerate(inorder)}

Step 3: Write the Recursive Builder

The recursive function will operate on index ranges rather than sliced lists. It needs to know:

The root is always at preorder[pre_start]. Its position in inorder, say root_idx, lets us compute the size of the left subtree as left_size = root_idx - in_start. Then:

Step 4: Put It All Together

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def buildTree(preorder, inorder):
    # Map each value in inorder to its index for O(1) lookups
    inorder_index_map = {val: idx for idx, val in enumerate(inorder)}

    def helper(pre_start, pre_end, in_start, in_end):
        # Base case: no elements to construct
        if pre_start > pre_end or in_start > in_end:
            return None

        # The first element in the current preorder range is the root
        root_val = preorder[pre_start]
        root = TreeNode(root_val)

        # Find the root's position in inorder
        root_idx = inorder_index_map[root_val]

        # Number of nodes in the left subtree
        left_size = root_idx - in_start

        # Recursively build left and right subtrees
        root.left = helper(
            pre_start + 1,
            pre_start + left_size,
            in_start,
            root_idx - 1
        )
        root.right = helper(
            pre_start + left_size + 1,
            pre_end,
            root_idx + 1,
            in_end
        )

        return root

    return helper(0, len(preorder) - 1, 0, len(inorder) - 1)

Step 5: Verify the Result

To confirm your reconstruction is correct, write helper functions that produce preorder and inorder traversals from the built tree and compare them with the inputs:

def preorder_traversal(root):
    result = []
    def dfs(node):
        if not node:
            return
        result.append(node.val)
        dfs(node.left)
        dfs(node.right)
    dfs(root)
    return result


def inorder_traversal(root):
    result = []
    def dfs(node):
        if not node:
            return
        dfs(node.left)
        result.append(node.val)
        dfs(node.right)
    dfs(root)
    return result


# Test
preorder = [3, 9, 20, 15, 7]
inorder  = [9, 3, 15, 20, 7]

root = buildTree(preorder, inorder)
print(preorder_traversal(root))  # [3, 9, 20, 15, 7]
print(inorder_traversal(root))   # [9, 3, 15, 20, 7]

If both printed lists match the original inputs, your reconstruction is correct.

Complexity Analysis

Understanding the time and space complexity of your solution is critical, especially in interviews and production code.

The naive approach — slicing lists at every recursive call — would have O(n²) time complexity due to the cost of copying sublists, plus O(n²) space for the slices. The index-based approach above avoids this entirely.

Edge Cases to Handle

Robust code must account for edge cases. Here are the most common ones:

Best Practices

Prefer Index Ranges Over List Slicing

List slicing in Python creates new lists, which is expensive in both time and memory. By passing index ranges to your recursive function, you avoid unnecessary allocations and keep the solution efficient.

Use a Hash Map for Lookups

Always precompute a dictionary for value-to-index mappings when you need repeated lookups in a list. This is a small change with a large performance impact.

Keep the Recursive Helper Self-Contained

Defining the recursive helper as an inner function keeps the public API clean. Users only need to call buildTree(preorder, inorder) without worrying about internal index parameters.

Consider Iterative Solutions for Very Large Trees

For extremely deep trees, recursion may hit Python's recursion limit (default 1000). In such cases, you can either increase the limit with sys.setrecursionlimit or rewrite the solution iteratively using an explicit stack. The iterative version is more complex but avoids stack overflow.

Validate Inputs in Production Code

If you are using this logic in a real application, add validation:

def buildTree(preorder, inorder):
    if len(preorder) != len(inorder):
        raise ValueError("preorder and inorder must have the same length")
    if set(preorder) != set(inorder):
        raise ValueError("preorder and inorder must contain the same values")
    if len(preorder) != len(set(preorder)):
        raise ValueError("values must be unique")

    # ... rest of the implementation

Iterative Approach (Bonus)

For completeness, here is an iterative solution that uses a stack to simulate the recursion. It is more advanced but useful when recursion depth is a concern:

def buildTreeIterative(preorder, inorder):
    if not preorder or not inorder:
        return None

    root = TreeNode(preorder[0])
    stack = [root]
    inorder_index = 0

    for i in range(1, len(preorder)):
        node = stack[-1]
        if node.val != inorder[inorder_index]:
            # Still in the left subtree
            node.left = TreeNode(preorder[i])
            stack.append(node.left)
        else:
            # Pop until we find the node whose right subtree we are entering
            while stack and stack[-1].val == inorder[inorder_index]:
                parent = stack.pop()
                inorder_index += 1
            parent.right = TreeNode(preorder[i])
            stack.append(parent.right)

    return root

This approach also runs in O(n) time and O(n) space, but it avoids recursion entirely, making it safer for very deep trees.

Conclusion

Constructing a binary tree from its preorder and inorder traversals is a powerful exercise in recursive thinking, index manipulation, and algorithmic optimization. By leveraging the structural clues embedded in each traversal order — the root-first property of preorder and the left-root-right property of inorder — you can reliably rebuild any binary tree with unique values. The key to an efficient solution is to avoid list slicing, use a hash map for constant-time lookups, and operate on index ranges throughout the recursion. With the recursive and iterative implementations covered here, plus an awareness of edge cases and best practices, you are well equipped to solve this problem confidently in interviews and apply the underlying techniques to broader tree and divide-and-conquer challenges.

— Ad —

Google AdSense will appear here after approval

← Back to all articles