Start learning free

LESSON 345 OF 443 · BINARY TREES

Lowest Common Ancestor in a BST in Swift: An O(h) Iterative Walk

Practice O(n) Read the full explanation

Given the root of a binary search tree (BST) and two values p and q that are both in the tree, return the value of their lowest common ancestor.

The lowest common ancestor of p and q is the deepest node that has both of them in its subtree. A node counts as its own ancestor: if the node holding p is above the node holding q, the answer is p.

In a BST, every value in a node's left subtree is smaller than the node's value, and every value in its right subtree is larger.

The trees below are written in level order (top to bottom, left to right), with nil for a missing child.

 

Example 1:

        6
      /   \
     2     8
    / \   / \
   0   4 7   9
      / \
     3   5

Input: root = [6,2,8,0,4,7,9,nil,nil,3,5], p = 2, q = 8
Output: 6
Explanation: 2 is in the left subtree of 6 and 8 is in its right subtree, so 6 is the deepest node that has both below it.

Example 2:

Input: root = [6,2,8,0,4,7,9,nil,nil,3,5], p = 2, q = 4
Output: 2
Explanation: 4 is in the subtree of 2, and a node counts as its own ancestor.

Example 3:

Input: root = [6,2,8,0,4,7,9,nil,nil,3,5], p = 5, q = 3
Output: 4
Explanation: 3 and 5 are the two children of 4, one on each side.

 

Constraints:

  • The number of nodes in the tree is in the range [1, 1000].
  • Each node value is between -104 and 104, and all values are different.
  • p and q are both values of nodes in the tree. They can be equal.

 

Follow up: Can you find it in O(h) time, where h is the height of the tree, using only O(1) extra space?
STEP 1 OF 6
    Ctrl + Enter to check

    SWIFT · BINARY TREES

    Lca In Bst in Swift, explained

    Problem overview

    The method lowestCommonAncestor receives the root of a binary search tree (a TreeNode?) and two values p and q that are both in the tree. It returns the value of their lowest common ancestor: the deepest node that has both values in its subtree. A node counts as its own ancestor, and p and q may be equal.

    The tree in the examples has root 6, with 2 and 8 below it; 2 has the children 0 and 4, 8 has 7 and 9, and 4 has 3 and 5. For p = 2 and q = 8 the answer is 6, since the two sit on different sides of the root. For p = 2 and q = 4 the answer is 2: 4 is inside the subtree of 2, and 2 is its own ancestor. For p = 5 and q = 3 the answer is 4, their parent.

    Approach: let the values choose the direction

    In a general binary tree you would have to search both subtrees for the two values. A binary search tree orders its values: everything in a node's left subtree is smaller than the node, everything in its right subtree is larger. So at any node you can tell where p and q live by comparing them with the node's value.

    Start at the root, which is above every node and so is always a common ancestor. Then ask one question at each node:

    • Both values are smaller than the node: both are in the left subtree, so the lowest common ancestor is there too. Move left.
    • Both are larger: move right.
    • Otherwise, they are on different sides, or one of them equals the node. Going down either way would leave one value behind, so this node is the answer.

    For p = 5 and q = 3: at 6, both are smaller, so go left to 2. At 2, both are larger, so go right to 4. At 4, 3 is smaller and 5 is larger, so they split here: the answer is 4. Each step goes one level down, so the walk takes at most as many steps as the tree is tall, with no recursion and no extra memory.

    Step by step

    1. Declare node with var and set it to root. It is a TreeNode? because it becomes nil when you step past a leaf.
    2. Loop with while let current = node. It repeats while node isn't nil, and each time binds the unwrapped node to current.
    3. Both smaller: if p < current.val && q < current.val, set node to current.left.
    4. Both larger: with else if, set node to current.right.
    5. Otherwise return current.val. The values are on different sides or one of them is this node's value, so this node is the deepest one with both below it.
    6. Return -1 after the loop. Because p and q are in the tree, the loop always returns before it ends. Swift needs a return on every path of a function that returns an Int.

    The solution in Swift

    class Solution {
        func lowestCommonAncestor(_ root: TreeNode?, _ p: Int, _ q: Int) -> Int {
            var node = root
            while let current = node {
                if p < current.val && q < current.val {
                    node = current.left
                } else if p > current.val && q > current.val {
                    node = current.right
                } else {
                    return current.val
                }
            }
            return -1
        }
    }
    

    Swift details worth knowing:

    • while let. It combines a loop with optional binding: while let current = node runs its body only when node holds a value, and inside the body current is a plain TreeNode. No ! is needed.
    • Optional children. current.left and current.right are TreeNode?, the same type as node, so they can be assigned to it directly.
    • Type inference. var node = root gets the type TreeNode? from root, so the loop can assign nil children to it.
    • Classes are references. TreeNode is a class, so node = current.left only moves the reference. The tree is never changed.
    • The return type is Int, so the method returns the value of the node, current.val, not the node.

    Complexity

    Time: O(h), where h is the height of the tree. Each pass goes one level down and does constant work. For a balanced tree that is O(log n); for a tree that is a single chain it is O(n).

    Space: O(1). Only node and current are kept. A recursive version would use O(h) stack space.

    Edge cases and common mistakes

    • One value is the ancestor. For p = 2 and q = 4, at node 2 neither "both smaller" nor "both larger" holds, since p equals the node's value, so the walk stops and returns 2.
    • Equal values. p = q = 4 returns 4: the walk goes 6, 2, 4 and stops at the node that holds the value.
    • A single-node tree. With root 5 and p = q = 5 the answer is 5.
    • Using <= and >= in the first two branches. That would move past a node that equals p or q, losing the case where a value is its own ancestor. The comparisons must be strict.
    • Joining with || instead of &&. "Either value smaller" doesn't put both in the left subtree.
    • Searching the whole tree. The ordering of the BST makes it unnecessary, and it would raise the cost from O(h) to O(n).
    • Splitting at the wrong level. For p = 0 and q = 5, the walk goes 6, then 2, where 0 is smaller and 5 is larger. The answer is 2, not 6.

    Testing

    • Root 6 tree, (2, 8) gives 6.
    • (2, 4) gives 2.
    • (5, 3) and (3, 5) give 4.
    • (4, 4) gives 4, and (0, 9) gives 6.
    • (7, 9) gives 8, and (0, 5) gives 2.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free