106. Construct Binary Tree from Inorder and Postorder Traversal
Problem Description
You are given two integer arrays that represent different traversals of the same binary tree:
inorder
: the inorder traversal of a binary tree (left → root → right)postorder
: the postorder traversal of the same tree (left → right → root)
Your task is to reconstruct and return the original binary tree from these two traversal sequences.
The key insight is that:
- In postorder traversal, the last element is always the root of the tree
- In inorder traversal, elements to the left of the root form the left subtree, and elements to the right form the right subtree
The solution uses a recursive approach with a hash table to efficiently locate elements. The function dfs(i, j, n)
reconstructs a subtree where:
i
is the starting index in the inorder arrayj
is the starting index in the postorder arrayn
is the number of nodes in the current subtree
The algorithm works by:
- Finding the root from the last element of the current postorder segment (
postorder[j + n - 1]
) - Locating this root's position
k
in the inorder array using the hash tabled
- Calculating the size of left subtree as
k - i
(elements before root in inorder) - Recursively building the left subtree with
dfs(i, j, k - i)
- Recursively building the right subtree with
dfs(k + 1, j + k - i, n - k + i - 1)
- Creating a new TreeNode with the root value and attaching the left and right subtrees
The hash table d
maps each value to its index in the inorder array for O(1) lookup time, making the overall solution efficient.
Intuition
The key to solving this problem lies in understanding the properties of different tree traversals:
In postorder traversal, we visit nodes in the order: left subtree → right subtree → root. This means the last element in any postorder sequence is always the root of that tree or subtree.
In inorder traversal, we visit nodes in the order: left subtree → root → right subtree. This means all elements to the left of the root belong to the left subtree, and all elements to the right belong to the right subtree.
These two properties give us a way to recursively reconstruct the tree:
-
Find the root: Look at the last element in the postorder array - that's our root.
-
Partition the trees: Find where this root appears in the inorder array. Everything before it forms the left subtree, everything after forms the right subtree.
-
Determine subtree boundaries: If the root is at position
k
in inorder, and we start from positioni
, then we havek - i
nodes in the left subtree. This tells us how to split both arrays for recursive calls. -
Recursive construction: For the left subtree, we take the first
k - i
elements from both arrays (adjusted for their starting positions). For the right subtree, we take the remaining elements except the root.
The challenge is tracking the correct indices as we recurse. Since we're working with subarrays conceptually, we need to maintain:
- Starting positions in both arrays (
i
for inorder,j
for postorder) - The number of nodes in the current subtree (
n
)
Using a hash table to store inorder positions eliminates the need to search for the root position each time, reducing our lookup from O(n) to O(1) and making the overall algorithm more efficient.
Solution Approach
The solution uses a hash table + recursion approach to efficiently reconstruct the binary tree.
Step 1: Build the Hash Table
First, we create a hash table d
that maps each value in the inorder array to its index:
d = {v: i for i, v in enumerate(inorder)}
This allows O(1) lookup of any node's position in the inorder traversal.
Step 2: Define the Recursive Function
We define dfs(i, j, n)
where:
i
= starting index in the inorder arrayj
= starting index in the postorder arrayn
= number of nodes in the current subtree
Step 3: Base Case
If n <= 0
, the subtree is empty, so we return None
.
Step 4: Find the Root
The root of the current subtree is always the last element in its postorder segment:
v = postorder[j + n - 1]
Step 5: Locate Root in Inorder
Use the hash table to find the root's position in the inorder array:
k = d[v]
Step 6: Calculate Subtree Sizes
- Left subtree size:
k - i
(nodes before the root in inorder) - Right subtree size:
n - k + i - 1
(total nodes minus root minus left subtree)
Step 7: Recursively Build Subtrees
Build the left subtree:
l = dfs(i, j, k - i)
- Inorder: starts at
i
, containsk - i
nodes - Postorder: starts at
j
, containsk - i
nodes
Build the right subtree:
r = dfs(k + 1, j + k - i, n - k + i - 1)
- Inorder: starts at
k + 1
(after root) - Postorder: starts at
j + k - i
(after left subtree nodes) - Contains
n - k + i - 1
nodes
Step 8: Construct and Return Node
Create a new TreeNode with value v
, left child l
, and right child r
:
return TreeNode(v, l, r)
The initial call dfs(0, 0, len(inorder))
starts with the entire arrays and reconstructs the complete tree.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the algorithm reconstructs a binary tree.
Given:
inorder = [9, 3, 15, 20, 7]
postorder = [9, 15, 7, 20, 3]
Goal: Reconstruct the binary tree that produces these traversals.
Step 1: Initialize
- Create hash table:
d = {9:0, 3:1, 15:2, 20:3, 7:4}
- Call
dfs(0, 0, 5)
to build the entire tree
Step 2: First call - dfs(0, 0, 5)
- Root value:
postorder[0 + 5 - 1] = postorder[4] = 3
- Root position in inorder:
k = d[3] = 1
- Left subtree size:
k - i = 1 - 0 = 1
node - Right subtree size:
5 - 1 + 0 - 1 = 3
nodes - Build left subtree:
dfs(0, 0, 1)
- Build right subtree:
dfs(2, 1, 3)
Step 3: Left subtree - dfs(0, 0, 1)
- Root value:
postorder[0 + 1 - 1] = postorder[0] = 9
- Root position in inorder:
k = d[9] = 0
- Left subtree size:
0 - 0 = 0
(no left child) - Right subtree size:
1 - 0 + 0 - 1 = 0
(no right child) - Return:
TreeNode(9)
Step 4: Right subtree - dfs(2, 1, 3)
- Root value:
postorder[1 + 3 - 1] = postorder[3] = 20
- Root position in inorder:
k = d[20] = 3
- Left subtree size:
3 - 2 = 1
node - Right subtree size:
3 - 3 + 2 - 1 = 1
node - Build left subtree:
dfs(2, 1, 1)
- Build right subtree:
dfs(4, 2, 1)
Step 5: Node 20's left child - dfs(2, 1, 1)
- Root value:
postorder[1 + 1 - 1] = postorder[1] = 15
- Root position in inorder:
k = d[15] = 2
- Left subtree size:
2 - 2 = 0
(no left child) - Right subtree size:
1 - 2 + 2 - 1 = 0
(no right child) - Return:
TreeNode(15)
Step 6: Node 20's right child - dfs(4, 2, 1)
- Root value:
postorder[2 + 1 - 1] = postorder[2] = 7
- Root position in inorder:
k = d[7] = 4
- Left subtree size:
4 - 4 = 0
(no left child) - Right subtree size:
1 - 4 + 4 - 1 = 0
(no right child) - Return:
TreeNode(7)
Step 7: Assemble the tree The recursion unwinds and builds:
3 / \ 9 20 / \ 15 7
Each recursive call identifies its root from postorder's last element, uses the hash table to split the inorder array, and recursively builds its subtrees using the calculated boundaries.
Solution Implementation
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
8from typing import List, Optional
9
10class Solution:
11 def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
12 """
13 Construct a binary tree from inorder and postorder traversal arrays.
14
15 Args:
16 inorder: List of node values in inorder traversal
17 postorder: List of node values in postorder traversal
18
19 Returns:
20 Root node of the constructed binary tree
21 """
22
23 def build_subtree(inorder_start: int, postorder_start: int, subtree_size: int) -> Optional[TreeNode]:
24 """
25 Recursively build a subtree.
26
27 Args:
28 inorder_start: Starting index in the inorder array for current subtree
29 postorder_start: Starting index in the postorder array for current subtree
30 subtree_size: Number of nodes in the current subtree
31
32 Returns:
33 Root node of the subtree
34 """
35 # Base case: empty subtree
36 if subtree_size <= 0:
37 return None
38
39 # The last element in postorder range is the root of current subtree
40 root_val = postorder[postorder_start + subtree_size - 1]
41
42 # Find root's position in inorder array
43 root_inorder_idx = inorder_index_map[root_val]
44
45 # Calculate size of left subtree
46 left_subtree_size = root_inorder_idx - inorder_start
47
48 # Recursively build left subtree
49 # Left subtree elements in inorder: [inorder_start, root_inorder_idx)
50 # Left subtree elements in postorder: [postorder_start, postorder_start + left_subtree_size)
51 left_child = build_subtree(
52 inorder_start,
53 postorder_start,
54 left_subtree_size
55 )
56
57 # Recursively build right subtree
58 # Right subtree elements in inorder: [root_inorder_idx + 1, inorder_start + subtree_size)
59 # Right subtree elements in postorder: [postorder_start + left_subtree_size, postorder_start + subtree_size - 1)
60 right_child = build_subtree(
61 root_inorder_idx + 1,
62 postorder_start + left_subtree_size,
63 subtree_size - left_subtree_size - 1
64 )
65
66 # Create and return the root node with its children
67 return TreeNode(root_val, left_child, right_child)
68
69 # Create a hashmap for O(1) lookup of element indices in inorder array
70 inorder_index_map = {val: idx for idx, val in enumerate(inorder)}
71
72 # Build the entire tree starting from index 0 with all elements
73 return build_subtree(0, 0, len(inorder))
74
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 // Map to store the index of each value in inorder array for O(1) lookup
18 private Map<Integer, Integer> inorderIndexMap = new HashMap<>();
19
20 // Global reference to postorder array
21 private int[] postorder;
22
23 /**
24 * Constructs a binary tree from inorder and postorder traversal arrays
25 * @param inorder array representing inorder traversal of the tree
26 * @param postorder array representing postorder traversal of the tree
27 * @return root node of the constructed binary tree
28 */
29 public TreeNode buildTree(int[] inorder, int[] postorder) {
30 this.postorder = postorder;
31 int arrayLength = inorder.length;
32
33 // Build a map for quick lookup of element indices in inorder array
34 for (int i = 0; i < arrayLength; ++i) {
35 inorderIndexMap.put(inorder[i], i);
36 }
37
38 // Start recursive construction with full array ranges
39 return buildSubtree(0, 0, arrayLength);
40 }
41
42 /**
43 * Recursively builds a subtree using the given range boundaries
44 * @param inorderStart starting index in the inorder array for current subtree
45 * @param postorderStart starting index in the postorder array for current subtree
46 * @param subtreeSize number of nodes in the current subtree
47 * @return root node of the constructed subtree
48 */
49 private TreeNode buildSubtree(int inorderStart, int postorderStart, int subtreeSize) {
50 // Base case: empty subtree
51 if (subtreeSize <= 0) {
52 return null;
53 }
54
55 // The last element in postorder range is the root of current subtree
56 int rootValue = postorder[postorderStart + subtreeSize - 1];
57
58 // Find the root's position in inorder array
59 int rootIndexInorder = inorderIndexMap.get(rootValue);
60
61 // Calculate the size of left subtree
62 int leftSubtreeSize = rootIndexInorder - inorderStart;
63
64 // Recursively build left subtree
65 // Left subtree in inorder: [inorderStart, rootIndexInorder)
66 // Left subtree in postorder: [postorderStart, postorderStart + leftSubtreeSize)
67 TreeNode leftChild = buildSubtree(inorderStart, postorderStart, leftSubtreeSize);
68
69 // Recursively build right subtree
70 // Right subtree in inorder: [rootIndexInorder + 1, inorderStart + subtreeSize)
71 // Right subtree in postorder: [postorderStart + leftSubtreeSize, postorderStart + subtreeSize - 1)
72 TreeNode rightChild = buildSubtree(rootIndexInorder + 1,
73 postorderStart + leftSubtreeSize,
74 subtreeSize - leftSubtreeSize - 1);
75
76 // Create and return the root node with its children
77 return new TreeNode(rootValue, leftChild, rightChild);
78 }
79}
80
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 {
13public:
14 TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
15 // Create a hash map to store value -> index mapping for inorder array
16 // This allows O(1) lookup to find the root position in inorder
17 unordered_map<int, int> inorderIndexMap;
18 int totalNodes = inorder.size();
19
20 // Build the index map for quick lookup
21 for (int i = 0; i < totalNodes; ++i) {
22 inorderIndexMap[inorder[i]] = i;
23 }
24
25 // Recursive function to build the tree
26 // Parameters:
27 // - inorderStart: starting index in inorder array for current subtree
28 // - postorderStart: starting index in postorder array for current subtree
29 // - subtreeSize: number of nodes in current subtree
30 function<TreeNode*(int, int, int)> buildSubtree = [&](int inorderStart, int postorderStart, int subtreeSize) -> TreeNode* {
31 // Base case: empty subtree
32 if (subtreeSize <= 0) {
33 return nullptr;
34 }
35
36 // The last element in postorder range is the root of current subtree
37 int rootValue = postorder[postorderStart + subtreeSize - 1];
38
39 // Find root's position in inorder array
40 int rootIndexInorder = inorderIndexMap[rootValue];
41
42 // Calculate the size of left subtree
43 int leftSubtreeSize = rootIndexInorder - inorderStart;
44
45 // Recursively build left subtree
46 // Left subtree in inorder: [inorderStart, rootIndexInorder)
47 // Left subtree in postorder: [postorderStart, postorderStart + leftSubtreeSize)
48 TreeNode* leftChild = buildSubtree(inorderStart, postorderStart, leftSubtreeSize);
49
50 // Recursively build right subtree
51 // Right subtree in inorder: [rootIndexInorder + 1, inorderStart + subtreeSize)
52 // Right subtree in postorder: [postorderStart + leftSubtreeSize, postorderStart + subtreeSize - 1)
53 int rightSubtreeSize = subtreeSize - leftSubtreeSize - 1;
54 TreeNode* rightChild = buildSubtree(rootIndexInorder + 1,
55 postorderStart + leftSubtreeSize,
56 rightSubtreeSize);
57
58 // Create and return the root node with its children
59 return new TreeNode(rootValue, leftChild, rightChild);
60 };
61
62 // Start building from the entire array
63 return buildSubtree(0, 0, totalNodes);
64 }
65};
66
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15/**
16 * Constructs a binary tree from its inorder and postorder traversal arrays
17 * @param inorder - Array representing inorder traversal of the tree
18 * @param postorder - Array representing postorder traversal of the tree
19 * @returns The root node of the constructed binary tree
20 */
21function buildTree(inorder: number[], postorder: number[]): TreeNode | null {
22 const arrayLength: number = inorder.length;
23
24 // Create a map to store the index of each value in the inorder array
25 // This allows O(1) lookup of where a value appears in the inorder traversal
26 const inorderIndexMap: Record<number, number> = {};
27 for (let i = 0; i < arrayLength; i++) {
28 inorderIndexMap[inorder[i]] = i;
29 }
30
31 /**
32 * Recursively builds the tree using divide and conquer approach
33 * @param inorderStart - Starting index in the inorder array for current subtree
34 * @param postorderStart - Starting index in the postorder array for current subtree
35 * @param subtreeSize - Number of nodes in the current subtree
36 * @returns Root node of the current subtree
37 */
38 const buildSubtree = (
39 inorderStart: number,
40 postorderStart: number,
41 subtreeSize: number
42 ): TreeNode | null => {
43 // Base case: empty subtree
44 if (subtreeSize <= 0) {
45 return null;
46 }
47
48 // The last element in postorder segment is the root of current subtree
49 const rootValue: number = postorder[postorderStart + subtreeSize - 1];
50
51 // Find the root's position in inorder array
52 const rootIndexInInorder: number = inorderIndexMap[rootValue];
53
54 // Calculate the size of left subtree
55 const leftSubtreeSize: number = rootIndexInInorder - inorderStart;
56
57 // Recursively build left subtree
58 // Left subtree elements in inorder: [inorderStart, rootIndexInInorder)
59 // Left subtree elements in postorder: [postorderStart, postorderStart + leftSubtreeSize)
60 const leftChild: TreeNode | null = buildSubtree(
61 inorderStart,
62 postorderStart,
63 leftSubtreeSize
64 );
65
66 // Recursively build right subtree
67 // Right subtree elements in inorder: [rootIndexInInorder + 1, end)
68 // Right subtree elements in postorder: [postorderStart + leftSubtreeSize, postorderStart + subtreeSize - 1)
69 const rightChild: TreeNode | null = buildSubtree(
70 rootIndexInInorder + 1,
71 postorderStart + leftSubtreeSize,
72 subtreeSize - 1 - leftSubtreeSize
73 );
74
75 // Create and return the root node with its children
76 return new TreeNode(rootValue, leftChild, rightChild);
77 };
78
79 // Start building the tree from the entire array
80 return buildSubtree(0, 0, arrayLength);
81}
82
Time and Space Complexity
Time Complexity: O(n)
The algorithm constructs a binary tree from inorder and postorder traversals. Breaking down the operations:
- Creating the dictionary
d
from the inorder list takesO(n)
time, where we iterate through alln
elements once - The recursive function
dfs
visits each node exactly once during tree construction - At each node, we perform constant time operations: finding the root value from postorder (
O(1)
), looking up its index in the dictionary (O(1)
), and creating a TreeNode (O(1)
) - The recursive calls partition the problem into left and right subtrees without overlap, ensuring each element is processed exactly once
Therefore, the total time complexity is O(n)
.
Space Complexity: O(n)
The space usage consists of:
- The dictionary
d
storing the index mapping of alln
elements from the inorder traversal:O(n)
- The recursion call stack depth, which in the worst case (skewed tree) can go up to
O(n)
, and in the best case (balanced tree) would beO(log n)
- The output tree structure itself contains
n
nodes:O(n)
Since we need to account for the worst case and the dominant factors, the overall space complexity is O(n)
.
Common Pitfalls
1. Incorrect Index Calculation for Right Subtree in Postorder Array
One of the most common mistakes is incorrectly calculating the starting index for the right subtree in the postorder array.
The Pitfall:
Developers often mistakenly use postorder_start + left_subtree_size + 1
as the starting index for the right subtree in postorder, thinking they need to skip the root element. However, the root is at the END of the postorder segment, not after the left subtree.
Incorrect Implementation:
# WRONG: This incorrectly skips an extra element right_child = build_subtree( root_inorder_idx + 1, postorder_start + left_subtree_size + 1, # ❌ Wrong! subtree_size - left_subtree_size - 1 )
Correct Implementation:
# CORRECT: Right subtree starts immediately after left subtree in postorder right_child = build_subtree( root_inorder_idx + 1, postorder_start + left_subtree_size, # ✓ Correct! subtree_size - left_subtree_size - 1 )
Why This Happens:
In postorder traversal, the sequence is: [left subtree] [right subtree] [root]. The root is the LAST element at index postorder_start + subtree_size - 1
, not in the middle. Therefore:
- Left subtree: indices
[postorder_start, postorder_start + left_subtree_size)
- Right subtree: indices
[postorder_start + left_subtree_size, postorder_start + subtree_size - 1)
- Root: index
postorder_start + subtree_size - 1
2. Handling Duplicate Values
The Pitfall: The algorithm assumes all node values are unique. If the tree contains duplicate values, the hash map will overwrite indices, leading to incorrect tree reconstruction.
Example Problem:
inorder = [2, 2, 3] postorder = [2, 3, 2] # The hash map becomes {2: 1, 3: 2}, losing the first occurrence of 2
Solution: Either ensure input constraints specify unique values, or modify the approach to handle duplicates by using a different strategy (like maintaining a pointer instead of using a hash map).
3. Off-by-One Errors in Subtree Size Calculation
The Pitfall: Forgetting to subtract 1 when calculating the right subtree size, since we need to exclude the root node.
Incorrect:
right_subtree_size = subtree_size - left_subtree_size # ❌ Includes root
Correct:
right_subtree_size = subtree_size - left_subtree_size - 1 # ✓ Excludes root
Debugging Tip: To avoid these index errors, it helps to trace through a small example manually or add assertions to verify that indices stay within bounds:
assert postorder_start + subtree_size - 1 < len(postorder), "Root index out of bounds"
assert left_subtree_size >= 0, "Invalid left subtree size"
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dnd svg Divide and Conquer The main idea of divide and conquer is to split the main problem into two smaller components assuming that each one of the components had already been solved recursively and then try to solve the bigger problem using the solutions
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Want a Structured Path to Master System Design Too? Don’t Miss This!