Editorial
Core insight
Construct Binary Tree from Preorder and Inorder Traversal · Tree Traversal Patterns (DFS & BFS)
Core Insight for Construct Binary Tree from Preorder and Inorder Traversal
The key to solving LeetCode 105 efficiently lies in understanding the relationship between the two input arrays:
- Preorder (
[Root, Left, Right]) gives us the order of creation. The next element to process in thepreorderarray is always the root of the current subtree. - Inorder (
[Left, Root, Right]) gives us the structure. Once we know the root value, its position in theinorderarray acts as a pivot. All elements to the left of this pivot belong to the left subtree; all elements to the right belong to the right subtree.
The optimization comes from eliminating the linear scan. Since the values are unique, we can pre-process the inorder array into a Hash Map (value index). This allows us to find the split point in time.
Visual Description:
Imagine the inorder array as a horizontal line segment representing the current range of available nodes. We maintain two pointers, left and right, defining this range. We pick the next available value from preorder as our root. We look up this root's index in our Hash Map. This index cuts the inorder line segment into two smaller segments: [left, index - 1] and [index + 1, right]. We then recurse: the first recursive call builds the left child using the left segment, and the second call builds the right child using the right segment.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 105: Construct Binary Tree from Preorder and Inorder Traversal Solution & Explanation
Problem Overview
TL;DR: The optimal solution utilizes the first element of the preorder array as the current root and uses a hash map to locate that root within the inorder array to define the boundaries of the left and right subtrees.
The problem asks us to rebuild a binary tree structure given two specific array representations: the preorder traversal (where the root appears before children) and the inorder traversal (where the root appears between the left and right children). This is a classic reconstruction problem that tests your understanding of tree traversal properties. The LeetCode 105 problem, "Construct Binary Tree from Preorder and Inorder Traversal," guarantees that the input arrays represent a valid, unique binary tree with unique integer values.
Brute Force Approach for Construct Binary Tree from Preorder and Inorder Traversal
The brute force strategy relies on the fundamental definition of the traversals but fails to optimize the search operation.
In a naive implementation, we identify the root from the preorder array (always the first element). Then, we scan the entire inorder array linearly to find the position of this root value. Once found, we calculate the sizes of the left and right subtrees and recursively call the function on the corresponding subarrays.
Many implementations of this approach also involve physically slicing the arrays (creating copies of subarrays) to pass to the recursive calls.
Naive Pseudo-code:
function buildTree(preorder, inorder):
if arrays are empty: return null
rootVal = preorder[0]
root = new TreeNode(rootVal)
// Linear scan to find split point
rootIndex = -1
for i from 0 to inorder.length:
if inorder[i] == rootVal:
rootIndex = i
break
// Array slicing (expensive)
leftInorder = inorder[0 to rootIndex]
rightInorder = inorder[rootIndex+1 to end]
leftPreorder = preorder[1 to 1 + length(leftInorder)]
rightPreorder = preorder[1 + length(leftInorder) to end]
root.left = buildTree(leftPreorder, leftInorder)
root.right = buildTree(rightPreorder, rightInorder)
return rootWhy this fails:
- Time Complexity: The linear scan to find
rootIndextakes . Since this happens for every node in the tree, the total time complexity degrades to in the worst case (a skewed tree). - Space Complexity: If array slicing is used, we allocate new memory for every recursive call, leading to significant memory overhead.
Algorithm Strategy: Tree Traversal Patterns (DFS & BFS)
We implement a recursive function that simulates a preorder traversal to build the tree.
- State Management: We maintain a global or class-level integer
preorderIndexthat tracks our progress through thepreorderarray. This index increments every time we create a new node. - Boundary Definition: The recursive function accepts
leftandrightinteger arguments. These represent the indices in theinorderarray that bound the current subtree. - Hash Map Optimization: Before recursion starts, we map every value in
inorderto its index. - Recursive Logic:
- Base Case: If
left > right, the range is invalid (empty subtree), so returnnull. - Node Creation: Get the value at
preorder[preorderIndex]and increment the index. Create a newTreeNodewith this value. - Subtree Construction: Retrieve the split index from the map.
- Recursively call the function with range
[left, splitIndex - 1]to attach toroot.left. - Recursively call the function with range
[splitIndex + 1, right]to attach toroot.right.
- Recursively call the function with range
- Return: Return the created
root.
- Base Case: If
Execution Flow
- Initialization: Create a Hash Map storing
value -> indexfor theinorderarray. InitializepreorderIndexto 0. - Initial Call: Call the recursive helper function with boundaries
0andn - 1. - Step 1 (Root Creation):
- Read
preorder[preorderIndex]. This is the root of the current subtree. - Increment
preorderIndex. - Instantiate the
TreeNode.
- Read
- Step 2 (Locate Pivot):
- Look up the root's value in the Hash Map to get
inorderIndex.
- Look up the root's value in the Hash Map to get
- Step 3 (Left Recursion):
- Call the helper for the left child using the range
lefttoinorderIndex - 1. - The recursion ensures all nodes in the left subtree are processed before any nodes in the right subtree (strictly following preorder logic).
- Call the helper for the left child using the range
- Step 4 (Right Recursion):
- Call the helper for the right child using the range
inorderIndex + 1toright.
- Call the helper for the right child using the range
- Step 5 (Backtracking):
- Once children are returned, attach them to the current node and return the node to the caller.
Proof of Correctness
The correctness relies on the unique properties of the traversals.
- Uniqueness: The problem guarantees unique values, ensuring the mapping from value to
inorderindex is bijective. - Base Case: An empty range correctly returns
null. - Inductive Step: Assume the algorithm correctly constructs subtrees for size . For a tree of size , the
preorderarray identifies the root. Theinordermap correctly partitions the remaining nodes into left and right sets. Since preorder traversal visits the root, then the entire left subtree, then the right subtree, incrementingpreorderIndexlinearly aligns perfectly with the recursive calls to the left then right children.
Pattern Reuse Notes
The Tree DFS pattern and the strategy of using traversal properties are applicable to several other problems:
- LeetCode 100: Same Tree - Uses recursive DFS to compare structural identity.
- LeetCode 101: Symmetric Tree - Uses recursive DFS to validate mirror symmetry.
- LeetCode 114: Flatten Binary Tree to Linked List - Uses preorder traversal logic to restructure the tree in-place.
- LeetCode 226: Invert Binary Tree - Uses postorder or preorder DFS to swap children recursively.
Understanding how to manipulate global state (like preorderIndex) while passing local boundaries (like left and right) is a recurring theme in tree construction and serialization problems.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 105
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13private:
14 std::unordered_map<int, int> inorderMap;
15 int preorderIndex;
16
17 TreeNode* build(vector<int>& preorder, int left, int right) {
18 // Base case: if the range is invalid, no node exists here
19 if (left > right) {
20 return nullptr;
21 }
22
23 // Get the current root value from preorder traversal
24 int rootValue = preorder[preorderIndex];
25 preorderIndex++;
26
27 TreeNode* root = new TreeNode(rootValue);
28
29 // Find the split point in inorder traversal
30 int splitIndex = inorderMap[rootValue];
31
32 // Recursively build left and right subtrees
33 // Important: Build left first because preorder is Root -> Left -> Right
34 root->left = build(preorder, left, splitIndex - 1);
35 root->right = build(preorder, splitIndex + 1, right);
36
37 return root;
38 }
39
40public:
41 TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
42 preorderIndex = 0;
43 inorderMap.clear();
44
45 // Map inorder values to their indices for O(1) lookup
46 for (int i = 0; i < inorder.size(); ++i) {
47 inorderMap[inorder[i]] = i;
48 }
49
50 return build(preorder, 0, inorder.size() - 1);
51 }
52};Java Solution for LeetCode 105
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 private Map<Integer, Integer> inorderMap;
18 private int preorderIndex;
19
20 public TreeNode buildTree(int[] preorder, int[] inorder) {
21 inorderMap = new HashMap<>();
22 preorderIndex = 0;
23
24 // Map inorder values to their indices for O(1) lookup
25 for (int i = 0; i < inorder.length; i++) {
26 inorderMap.put(inorder[i], i);
27 }
28
29 return build(preorder, 0, inorder.length - 1);
30 }
31
32 private TreeNode build(int[] preorder, int left, int right) {
33 // Base case: if the range is invalid, no node exists here
34 if (left > right) {
35 return null;
36 }
37
38 // Get the current root value from preorder traversal
39 int rootValue = preorder[preorderIndex];
40 preorderIndex++;
41
42 TreeNode root = new TreeNode(rootValue);
43
44 // Find the split point in inorder traversal
45 int splitIndex = inorderMap.get(rootValue);
46
47 // Recursively build left and right subtrees
48 root.left = build(preorder, left, splitIndex - 1);
49 root.right = build(preorder, splitIndex + 1, right);
50
51 return root;
52 }
53}Python Solution for LeetCode 105
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8class Solution:
9 def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
10 # Map inorder values to their indices for O(1) lookup
11 inorder_map = {val: idx for idx, val in enumerate(inorder)}
12 self.preorder_index = 0
13
14 def build(left: int, right: int) -> Optional[TreeNode]:
15 # Base case: if the range is invalid, no node exists here
16 if left > right:
17 return None
18
19 # Get the current root value from preorder traversal
20 root_val = preorder[self.preorder_index]
21 self.preorder_index += 1
22
23 root = TreeNode(root_val)
24
25 # Find the split point in inorder traversal
26 split_index = inorder_map[root_val]
27
28 # Recursively build left and right subtrees
29 # The order matters: Preorder traverses Left then Right
30 root.left = build(left, split_index - 1)
31 root.right = build(split_index + 1, right)
32
33 return root
34
35 return build(0, len(inorder) - 1)