Start learning free

LESSON 343 OF 441 · BINARY TREES

Lowest Common Ancestor in a BST in C#: Iterative Walk in O(h)

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 null for a missing child.

 

Example 1:

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

Input: root = [6,2,8,0,4,7,9,null,null,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,null,null,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,null,null,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

    C# · BINARY TREES

    Lca In Bst in C#, explained

    Problem overview

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

    In a binary search tree, every value in a node's left subtree is smaller than the node, and every value in its right subtree is larger. Take the tree with root 6, children 2 and 8, then 0 and 4 under 2, 7 and 9 under 8, and 3 and 5 under 4:

    • For p = 2 and q = 8 the answer is 6: they are on opposite sides of the root.
    • For p = 2 and q = 4 the answer is 2, because 4 is below the node 2 and the node 2 is its own ancestor.
    • For p = 5 and q = 3 the answer is 4: its two children are 3 and 5.

    Approach: follow the values down until the paths split

    Any ordinary tree would need a search of both subtrees. The BST ordering makes that unnecessary, because comparing a value with a node tells exactly which subtree holds it.

    Start at the root, which is above every node and so is a common ancestor. Then look at the current node:

    • If both p and q are smaller than the node's value, both are in its left subtree, and so is their lowest common ancestor. Move left.
    • If both are larger, move right.
    • Otherwise the two values are on different sides of the node, or one of them is the node's own value. Going into either child would leave one of the values outside, so this node is the lowest one that has both below it.

    Because the walk stops at the first node where the values do not both go the same way, it finds the deepest such node. It needs no recursion and no list of ancestors.

    For p = 5 and q = 3 in the tree above: at 6, both are smaller, so move to 2. At 2, both are larger, so move to 4. At 4, 5 is larger and 3 is smaller, so the answer is 4.

    Step by step

    1. Start with node = root. It is the current position in the walk.
    2. Loop while node != null. Each pass looks at one node and either returns or goes one level down.
    3. Both values smaller. If p < node.val && q < node.val, set node = node.left.
    4. Both values larger. With else if, if p > node.val && q > node.val, set node = node.right.
    5. Anything else. In the else branch, return node.val. The values are split by this node, or one of them equals it.
    6. Return -1 after the loop. The loop always returns first, since p and q are in the tree. The line is there because C# requires an int method to return a value on every path.

    The solution in C#

    class Solution {
        public int LowestCommonAncestor(TreeNode root, int p, int q) {
            TreeNode node = root;
            while (node != null) {
                if (p < node.val && q < node.val) {
                    node = node.left;
                } else if (p > node.val && q > node.val) {
                    node = node.right;
                } else {
                    return node.val;
                }
            }
            return -1;
        }
    }
    

    The C# details worth knowing:

    • p and q are plain ints, not nodes. They are compared directly with node.val. There is no need to look nodes up first.
    • The compiler checks every path. C# rejects a method with a return type if some path reaches the end without returning (error CS0161, "not all code paths return a value"). It can't prove that the loop always returns, so the final return -1; is required, even though it never runs on valid input.
    • Reassigning node doesn't change the tree. node is a local reference. Moving it to node.left changes where the variable points, not any node.

    Complexity

    Let h be the height of the tree.

    Time: O(h). Each pass moves one level down, so there are at most h passes. For a balanced tree that is about log n. For a tree that is a single chain, it is n.

    Space: O(1). The only extra memory is the variable node. A recursive version does the same moves but uses up to h stack frames, so the loop is the better choice for the follow-up.

    Edge cases and common mistakes

    • One value is an ancestor of the other. For p = 2 and q = 4 in the example, at the node 2 the value p equals node.val, so neither check passes and the method returns 2. This is why the first two conditions use strict comparisons.
    • Equal values. With p == q, such as 3 and 3, the walk goes down to the node 3 and returns 3. With p = q = 6 it returns 6 at once.
    • A one-node tree. With root 1 and p = q = 1, the method returns 1.
    • The order of p and q. It makes no difference: (5, 3) and (3, 5) give the same answer, since both conditions treat the two values the same way.
    • The values on the two extremes. For p = 0 and q = 9 the answer is the root, 6.
    • Using <= or >=. The walk would continue past a node that equals p or q and could skip the right answer.
    • Using || instead of &&. The walk would go down as soon as one value points that way, and leave the other one behind.
    • Searching both subtrees. That solves the problem for any binary tree, but it ignores the ordering and costs O(n).

    Testing

    With the tree [6, 2, 8, 0, 4, 7, 9, null, null, 3, 5]:

    • p = 2, q = 8 gives 6.
    • p = 2, q = 4 gives 2.
    • p = 5, q = 3 gives 4.
    • p = 3, q = 3 gives 3.
    • p = 0, q = 5 gives 2.
    • p = 7, q = 9 gives 8.
    • p = 0, q = 9 gives 6.
    Keep going with C#About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free