Start learning free

LESSON 422 OF 541 · BINARY TREES

Construct Binary Tree from Preorder and Inorder in Swift: O(n)

Practice O(n) Read the full explanation

Two arrays describe the same binary tree:

  • preorder lists the values in preorder: first a node, then its whole left subtree, then its whole right subtree.
  • inorder lists the values in inorder: first a node's whole left subtree, then the node, then its whole right subtree.

All the values are different. Build the tree and return its root.

The tests print the tree you return level by level, from left to right, with null where a child is missing and the trailing nulls left out. For example, [1,2,null,3] is the tree with root 1, 2 as its left child and 3 as the left child of 2.

 

Example 1:

Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
    3
   / \
  9   20
     /  \
    15   7

Explanation: preorder starts with 3, so 3 is the root. In inorder, 9 is left of 3 and 15, 20, 7 are right of it: 9 is the left subtree and 15, 20, 7 are the right subtree. The right subtree's values come next in preorder, starting with 20, its root; in inorder 15 is left of 20 and 7 is right of it.

Example 2:

Input: preorder = [-1], inorder = [-1]
Output: [-1]
Explanation: A single value is a tree with only a root.

Example 3:

Input: preorder = [1,2,3], inorder = [3,2,1]
Output: [1,2,null,3]
      1
     /
    2
   /
  3

Explanation: In inorder, every value comes before its parent, so each node has only a left child. The same preorder with inorder = [1,2,3] would give a line going right: [1,null,2,null,3].

 

Constraints:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • Every value is between -3000 and 3000, and the values are all different.
  • preorder and inorder are the preorder and inorder traversals of the same tree.
  • The tree is at most 400 levels deep.

Note:

The solution should run in O(n) time, where n is the number of values: build each node once, and find where a value sits in inorder with a Dictionary instead of a search.

STEP 1 OF 13
    Ctrl + Enter to check

    SWIFT · BINARY TREES

    Construct Binary Tree From Preorder And Inorder Traversal in Swift, explained

    Problem overview

    buildTree receives two arrays of Int, preorder and inorder, both listing the values of the same binary tree, and returns the tree's root as a TreeNode?. All values are different. The lesson provides the TreeNode class, with val, left, right and init(_ x: Int).

    In the first example, preorder = [3,9,20,15,7] and inorder = [9,3,15,20,7]. The result, printed level by level, is [3,9,20,null,null,15,7]: 3 is the root, 9 is its left child, and 20 is its right child with the children 15 and 7.

    Approach: the root comes first in preorder

    Preorder lists a node, then its whole left subtree, then its whole right subtree. So the first value of preorder is always the root. Inorder lists the left subtree, then the node, then the right subtree. Once you know where the root sits in inorder, everything to its left is the left subtree and everything to its right is the right subtree.

    Each subtree fills one unbroken stretch of inorder, so two indexes, lo and hi, describe it. In preorder, the roots appear in exactly the order the recursion needs them: this subtree's root, then the roots of the left subtree, then those of the right subtree. A single counter that moves forward through preorder therefore hands out the next root every time.

    Finding the root's position in inorder by scanning would cost O(n) per node and O(n²) overall. Since the values are all different, a dictionary from value to inorder index answers in constant time, and the whole build is O(n).

    Step by step

    1. The index map. A property inorderIndex, an empty [Int: Int], will hold each value's position in inorder. A property is visible to every method of the class, so the helper can use it.
    2. The preorder counter. A property preIndex starting at 0 marks the next root to use.
    3. Fill the map. In buildTree, loop i over 0..<inorder.count and store i under the key inorder[i].
    4. Start the build. Return what the helper build makes from preorder and the whole range of inorder, from 0 to inorder.count - 1.
    5. The helper's signature. build(_ preorder: [Int], _ lo: Int, _ hi: Int) -> TreeNode? builds the subtree whose values fill inorder[lo...hi].
    6. Empty range. If lo > hi, there are no values, so return nil. This ends the recursion below every leaf.
    7. Take the next root. Read preorder[preIndex] into value, then move preIndex forward by one.
    8. Create the node for value.
    9. Find the split. Look up inorderIndex[value] as mid. A dictionary lookup returns an optional, so ! unwraps it; every value is in the map.
    10. Left subtree. Call build for the range lo to mid - 1 and store the result in node.left. The left call comes first because its roots follow this node in preorder.
    11. Right subtree. Call build for mid + 1 to hi. By now the left call has used all of its roots, so preIndex points at the right subtree's root.
    12. Return the node.

    The solution in Swift

    class Solution {
        var inorderIndex = [Int: Int]()
        var preIndex = 0
    
        func build(_ preorder: [Int], _ lo: Int, _ hi: Int) -> TreeNode? {
            if lo > hi {
                return nil
            }
            let value = preorder[preIndex]
            preIndex += 1
            let node = TreeNode(value)
            let mid = inorderIndex[value]!
            node.left = build(preorder, lo, mid - 1)
            node.right = build(preorder, mid + 1, hi)
            return node
        }
    
        func buildTree(_ preorder: [Int], _ inorder: [Int]) -> TreeNode? {
            for i in 0..<inorder.count {
                inorderIndex[inorder[i]] = i
            }
            return build(preorder, 0, inorder.count - 1)
        }
    }
    

    The Swift details worth a closer look:

    • Properties and let nodes. Solution is a class, so its methods can change preIndex and inorderIndex without any special keyword. TreeNode is a class too, so let node = TreeNode(value) still lets you assign node.left and node.right: let fixes the reference, not the object's contents.
    • Optional from a dictionary. inorderIndex[value] has type Int?, because a missing key gives nil. The ! would crash for a value that isn't in the map, which can't happen when the two arrays describe the same tree.
    • The order of the two recursive calls matters. The calls share preIndex, which only works if the left subtree is built before the right one. Swapping the two lines gives wrong trees.
    • Passing arrays down. Arrays are value types, but passing preorder to build doesn't copy the elements: Swift copies an array only when someone changes it, and nothing does here.
    • Ranges. 0..<inorder.count excludes the upper bound, so the loop visits every index exactly once. The recursion uses inclusive bounds (lo and hi are both valid indexes), which is why the empty case is lo > hi.

    Complexity

    Time: O(n). Filling the map visits each index once. build creates exactly one node per value and does a constant amount of other work per call, plus one nil return per missing child.

    Space: O(n). The dictionary holds n entries, and the tree itself has n nodes. The recursion is as deep as the tree, at most 400 levels here.

    Edge cases and common mistakes

    • A single value. preorder = [-1] and inorder = [-1] give [-1]: both child ranges are empty.
    • Shapes that are all one side. With preorder = [1,2,3], inorder = [3,2,1] gives [1,2,null,3] (every node has only a left child), while inorder = [1,2,3] gives [1,null,2,null,3].
    • The same preorder, a different tree. inorder = [2,1,3] with preorder = [1,2,3] gives [1,2,3]. The inorder array alone decides where each root splits its range.
    • Using mid against preorder. mid is a position in inorder. The next root is found with the separate counter preIndex.
    • Reusing one Solution object. preIndex is never set back to 0, so after one call it points past the end of preorder. A second buildTree on the same object reads out of bounds and crashes. Use a new Solution for each tree, or reset preIndex to 0 at the start of buildTree.
    • Scanning for the root. Searching inorder with a loop for every node still works but takes O(n²) time.

    Testing

    • preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] gives [3,9,20,null,null,15,7].
    • preorder = [-1], inorder = [-1] gives [-1].
    • preorder = [1,2,3], inorder = [3,2,1] gives [1,2,null,3].
    • preorder = [1,2,3], inorder = [1,2,3] gives [1,null,2,null,3].
    • preorder = [1,2,4,5,3], inorder = [4,2,5,1,3] gives [1,2,3,4,5].
    Keep going with Swift500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free