106. Construct Binary Tree from Inorder and Postorder Traversal - Explanation
Description
You are given two integer arrays inorder and postorder where inorder is the inorder traversal of a binary tree and postorder is the postorder traversal of the same tree, construct and return the binary tree.
Example 1:
Input: inorder = [2,1,3,4], postorder = [2,4,3,1]
Output: [1,2,3,null,null,null,4]Example 2:
Input: inorder = [1], postorder = [1]
Output: [1]Constraints:
1 <= postorder.length == inorder.length <= 3000.-3000 <= inorder[i], postorder[i] <= 3000inorderandpostorderconsist of unique values.- Each value of
postorderalso appears ininorder. inorderis guaranteed to be the inorder traversal of the tree.postorderis guaranteed to be the postorder traversal of the tree.
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Binary Tree Traversals - Understanding inorder (left-root-right) and postorder (left-right-root) traversal orders
- Recursion / DFS - Building trees recursively by dividing the problem into subtrees
- Hash Maps - Mapping values to indices for O(1) lookup of root positions in inorder array
- Array Slicing - Dividing arrays into subarrays for left and right subtree construction
1. Depth First Search
Intuition
The last element of postorder is always the root. Once we identify the root, we find it in the inorder array. Everything to the left of the root in inorder belongs to the left subtree, and everything to the right belongs to the right subtree. We recursively apply this logic, slicing the arrays accordingly.
Algorithm
- If
postorderorinorderis empty, returnnull. - Create the root node using the last element of
postorder. - Find the index
midof the root value ininorder. - Recursively build the left subtree using
inorder[0:mid]andpostorder[0:mid]. - Recursively build the right subtree using
inorder[mid+1:]andpostorder[mid:-1]. - 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, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
if not postorder or not inorder:
return None
root = TreeNode(postorder[-1])
mid = inorder.index(postorder[-1])
root.left = self.buildTree(inorder[:mid], postorder[:mid])
root.right = self.buildTree(inorder[mid + 1:], postorder[mid:-1])
return rootTime & Space Complexity
- Time complexity:
- Space complexity:
2. Hash Map + Depth First Search
Intuition
The previous approach has O(n) lookup time when finding the root in inorder. By precomputing a hash map that stores each value's index in inorder, we achieve O(1) lookups. We also avoid creating new arrays by using index boundaries instead.
Algorithm
- Build a hash map mapping each value in
inorderto its index. - Maintain a pointer
postIdxstarting at the end ofpostorder. - Define
dfs(l, r)that builds the tree for theinorderrange[l, r]:- If
l > r, returnnull. - Pop the current root from
postorder(decrementpostIdx), create a node. - Find the root's index in
inorderusing the hash map. - Build the right subtree first (since we're processing
postorderfrom the end), then the left subtree.
- If
- Return the result of
dfs(0, n-1).
# 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, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
inorderIdx = {v: i for i, v in enumerate(inorder)}
def dfs(l, r):
if l > r:
return None
root = TreeNode(postorder.pop())
idx = inorderIdx[root.val]
root.right = dfs(idx + 1, r)
root.left = dfs(l, idx - 1)
return root
return dfs(0, len(inorder) - 1)Time & Space Complexity
- Time complexity:
- Space complexity:
3. Depth First Search (Optimal)
Intuition
We can eliminate the hash map by using a boundary-based approach. Instead of explicitly finding the root's position, we pass a "limit" value that tells us when to stop building the current subtree. When we encounter the limit in inorder, we know the current subtree is complete. This approach processes both arrays from right to left.
Algorithm
- Initialize pointers
postIdxandinIdxboth at the last index. - Define
dfs(limit)that builds the subtree bounded bylimit:- If
postIdx < 0, returnnull. - If
inorder[inIdx]equalslimit, decrementinIdxand returnnull(subtree boundary reached). - Create a node with
postorder[postIdx], decrementpostIdx. - Build the right subtree with limit set to the current node's value.
- Build the left subtree with the original limit.
- Return the node.
- If
- Call
dfswith an impossible limit value (like infinity) and return the result.
# 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, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
postIdx = inIdx = len(postorder) - 1
def dfs(limit):
nonlocal postIdx, inIdx
if postIdx < 0:
return None
if inorder[inIdx] == limit:
inIdx -= 1
return None
root = TreeNode(postorder[postIdx])
postIdx -= 1
root.right = dfs(root.val)
root.left = dfs(limit)
return root
return dfs(float('inf'))Time & Space Complexity
- Time complexity:
- Space complexity: for recursion stack.
Common Pitfalls
Building Left Subtree Before Right When Using Postorder Index
When processing postorder from right to left, you must build the right subtree before the left subtree. The postorder array is structured as [left, right, root], so when traversing backwards, we encounter right subtree elements first.
# Wrong: Building left before right
root.left = dfs(l, idx - 1)
root.right = dfs(idx + 1, r) # postIdx is now wrong
# Correct: Build right first
root.right = dfs(idx + 1, r)
root.left = dfs(l, idx - 1)Off-by-One Errors in Array Slicing
When slicing arrays for recursive calls, it's easy to miscalculate the postorder boundaries. The left subtree has mid elements, so postorder[:mid] is correct for the left subtree, and postorder[mid:-1] (excluding the root) is correct for the right subtree.
# Wrong: Including the root in right subtree
root.right = self.buildTree(inorder[mid + 1:], postorder[mid:])
# Correct: Exclude the root (last element)
root.right = self.buildTree(inorder[mid + 1:], postorder[mid:-1])Assuming Preorder Logic Works for Postorder
In preorder traversal, the root is at the beginning of the array, but in postorder, the root is at the end. Using postorder[0] instead of postorder[-1] as the root will produce an incorrect tree.
Sign in to join the discussion