The Problem
Given two integer arrays preorder
and inorder
where preorder
is the preorder traversal of a binary tree and inorder
is the inorder traversal of the same tree, construct and return the binary tree.
Examples
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
Input: preorder = [-1], inorder = [-1]
Output: [-1]
Constraints
- 1 <= preorder.length <= 3000
- inorder.length == preorder.length
- -3000 <= preorder[i], inorder[i] <= 3000
preorder
andinorder
consist of unique values.- Each value of
inorder
also appears inpreorder
. preorder
is guaranteed to be the preorder traversal of the tree.inorder
is guaranteed to be the inorder traversal of the tree.
Depth First Search Solution
func buildTree(_ preorder: [Int], _ inorder: [Int]) -> TreeNode? {
if preorder.isEmpty || inorder.isEmpty {
return nil
}
let rootVal = preorder[0]
let root = TreeNode(rootVal)
guard let mid = inorder.firstIndex(of: rootVal) else {
return nil
}
if mid > 0 {
root.left = buildTree(Array(preorder[1...mid]), Array(inorder[0..<mid]))
}
if mid < inorder.count - 1 {
root.right = buildTree(Array(preorder[mid + 1..<preorder.count]), Array(inorder[mid + 1..<inorder.count]))
}
return root
}
Explanation
We can solve this problem by understanding how preorder and inorder traversal work.
-
Preorder Traversal:
For preorder traversal, we start from theroot
, then move to theleft
subtree, and after that to theright
subtree. For example, with the inputpreorder = [3,9,20,15,7]
, theroot
is always the first element in the array (in this case,3
). Knowing this, we can recursively reconstruct theleft
andright
subtrees. -
Inorder Traversal:
Inorder traversal starts with theleft
subtree first, then moves to theroot
, and after that to theright
subtree. For example, with the inputinorder = [9,3,15,20,7]
, every value to the left of theroot
belongs to theleft
subtree, and every value to the right of theroot
belongs to theright
subtree.
Using this information, we can find the root
and then partition the arrays into subarrays to reconstruct the tree.
This is not a very efficient solution and takes O(n^2) time and space, but we can optimize it.
Time/Space Complexity
- Time complexity: O(n^2)
- Space complexity: O(n^2)
DFS Optimal Solution
func buildTree(_ preorder: [Int], _ inorder: [Int]) -> TreeNode? {
var indices: [Int: Int] = [:]
for (i, v) in inorder.enumerated() {
indices.updateValue(i, forKey: v)
}
var preIdx = 0
func dfs(_ l: Int, _ r: Int) -> TreeNode? {
if l > r {
return nil
}
let rootVal = preorder[preIdx]
preIdx += 1
let root = TreeNode(rootVal)
let mid = indices[rootVal]!
root.left = dfs(l, mid - 1)
root.right = dfs(mid + 1, r)
return root
}
return dfs(0, inorder.count - 1)
}
Explanation
We can solve this problem in a more optimal way by using the DFS algorithm and a hashmap. The logic behind the solution stays the same: we find the root value
from the preorder
array, then find the mid
index in the inorder
array, and build the tree using recursion.
Time/Space Complexity
- Time complexity: O(n)
- Space complexity: O(n)