150NeetCode 150

52.Lowest Common Ancestor of a BST

MediumBlind 75Trees ยท 7/15

Problem

Given a binary search tree (BST) and two nodes p and q in it, return their lowest common ancestor (LCA): the deepest node that has both p and q as descendants. A node is allowed to be a descendant of itself, so if p is an ancestor of q, the answer is p.

Examples

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 the right subtree, so they split at 6.
Input:  root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
Output: 2
Explanation: 4 is a descendant of 2, and a node is a descendant of itself.
Input:  root = [2,1], p = 2, q = 1
Output: 2

Constraints

  • The number of nodes in the tree is in the range [2, 10^5].
  • -10^9 <= Node.val <= 10^9
  • All Node.val are unique.
  • p != q, and both p and q exist in the BST.

๐Ÿ’ก Key Insight

In a BST, everything smaller than a node lives on its left and everything larger lives on its right. Starting from the root, if both p and q are smaller, the LCA is in the left subtree; if both are larger, it is in the right subtree. The first node where they split (one on each side, or the node equals one of them) is the LCA. That gives an O(h) walk from the root with O(1) extra space.

Generic Binary Tree LCA

TimeO(n)SpaceO(h)

Intuition

Ignore the BST ordering and solve the general problem: recursively search both subtrees. If a node is p or q, return it. If the left and right recursive calls both return non-null, the current node is where the two targets split, so it is the LCA. Otherwise, pass up whichever side found something.

Algorithm

  1. If root is null, p or q, return root.
  2. left = lca(root.left), right = lca(root.right).
  3. If both are non-null, return root.
  4. Return left != null ? left : right.

Visualization

p = 2, q = 8 on root 6

lca(6): left  = lca(2) -> returns 2 (node is p)
        right = lca(8) -> returns 8 (node is q)
        both non-null -> answer 6

Code

java
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) {
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if (left != null && right != null) {
            return root; // p and q found on different sides
        }
        return left != null ? left : right;
    }
}

Complexity

  • Time: O(n) โ€” in the worst case every node is visited; the BST property is never used.
  • Space: O(h) โ€” recursion depth.

Complexity Comparison

Approach Time Space
Generic Binary Tree LCA O(n) O(h)
Recursive BST Descent O(h) O(h)
Iterative BST Descent O(h) O(1)

Common Pitfalls

  • Forgetting that a node is its own descendant: when node == p (or q), it is already the LCA โ€” the "else" branch handles this because the strict comparisons fail.
  • Using <= / >= in the descent conditions, which walks past p or q when one of them equals the current node.
  • Ignoring the BST property and paying O(n) when O(h) is possible.
  • Values go down to -10^9; subtracting values (e.g. (p.val - node.val) * (q.val - node.val) > 0) can overflow int โ€” compare instead, or use long.