You are given two integer arrays preorder and inorder.
preorder is the preorder traversal of a binary treeinorder is the inorder traversal of the same treeRebuild 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
You should aim for a solution with O(n) time and O(n) space, where n is the number of nodes.
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.
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?
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)?
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?
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.
Before attempting this problem, you should be comfortable with:
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.
null (base case).preorder.inorder (call it mid).preorder[1:mid+1] and inorder[0:mid].preorder[mid+1:] and inorder[mid+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, 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 rootIn 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.
inorder to its index.0.dfs(l, r) for the inorder range [l, r].l > r, return null (base case).preorder at the current index, increment the index.mid).dfs(l, mid-1) and right subtree with dfs(mid+1, r).# 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)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.
preIdx for preorder and inIdx for inorder.dfs(limit) that builds a subtree until it hits the limit value.preIdx >= n, return null (no more nodes).inorder[inIdx] == limit, increment inIdx and return null (subtree complete).preorder[preIdx], increment preIdx.dfs(root.val) since nodes less than root appear before it in inorder.dfs(limit) using the original limit.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'))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.
curr to point to it.preorder with index i and inorder with index j.preorder[i] and attach it as curr's right child, then move curr to this new node.preorder[i] does not match inorder[j], keep creating left children (storing parent in right pointer).j. While curr.right exists and matches inorder[j], clear the temporary right link and move up.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.rightThe 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]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.
The root always comes from preorder (first element), while the split point is found in inorder. Swapping these roles produces an incorrect tree structure.