Lowest Common Ancestor of a Binary Search Tree
Problem Description
Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given nodes p and q in the BST.
According to the definition of LCA: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).”
Examples
Example 1:Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 Output: 6 Explanation: The LCA of nodes 2 and 8 is 6.
Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 Output: 2 Explanation: The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.
Constraints
- The number of nodes in the tree is in the range
[2, 10⁵]. -10⁹ ≤ Node.val ≤ 10⁹- All
Node.valare unique. pandqwill exist in the BST.p != q
Leveraging BST Properties to Find the Split Point
In a standard binary tree, finding the LCA requires traversing the entire tree to look for p and q. In a Binary Search Tree (BST), we can exploit the ordering property: for any node, all left descendants are smaller, and all right descendants are larger.
Start at the root. Compare p and q values against the root:
- If both
pandqare smaller than the root, the LCA must be in the left subtree. - If both
pandqare larger than the root, the LCA must be in the right subtree. - If they lie on opposite sides (one is smaller, one is larger), or if one of them is equal to the root, the current node is the split point, which is the Lowest Common Ancestor.
This reduces the search to a simple path traversal.
Solution 1: Iterative BST Traversal
Traverse down the tree by comparing values, updating the current node until you reach the split point.
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
TreeNode curr = root;
while (curr != null) {
if (p.val < curr.val && q.val < curr.val) {
curr = curr.left; // both are in the left subtree
} else if (p.val > curr.val && q.val > curr.val) {
curr = curr.right; // both are in the right subtree
} else {
return curr; // split point found: this is the LCA
}
}
return null;
}
}class Solution:
def lowestCommonAncestor(
self, root: TreeNode, p: TreeNode, q: TreeNode
) -> TreeNode:
curr = root
while curr:
if p.val < curr.val and q.val < curr.val:
curr = curr.left # both are in the left subtree
elif p.val > curr.val and q.val > curr.val:
curr = curr.right # both are in the right subtree
else:
return curr # split point found: this is the LCA
return Noneclass Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode* curr = root;
while (curr) {
if (p->val < curr->val && q->val < curr->val) {
curr = curr->left; // both are in the left subtree
} else if (p->val > curr->val && q->val > curr->val) {
curr = curr->right; // both are in the right subtree
} else {
return curr; // split point found: this is the LCA
}
}
return nullptr;
}
};Complexity Analysis:
- Time Complexity: O(h) where h is the tree height. We traverse one path from root to leaf in the worst case. For a balanced BST, this is O(log n); for a skewed BST, this is O(n).
- Space Complexity: O(1) auxiliary space as we only use a pointer variable.
Where it breaks: If the input tree violates the BST properties (e.g. it is a standard binary tree that is not sorted), this logic fails completely. For general binary trees, you must use backtracking depth-first search (LeetCode 236).
Common Mistakes
- Writing a complex DFS that traverses the entire tree. While a general binary tree LCA solution works on a BST, it runs in O(n) time and misses the opportunity to optimize to O(h) with O(1) space.
- Forgetting that a node can be an ancestor of itself. If
pis the parent ofq, the split checkp.val > curr.val && q.val > curr.valwill evaluate to false whencurr == p. The algorithm correctly returnspimmediately. - Null checks inside the loop when not needed. Since the problem constraints guarantee that
pandqexist in the tree,currwill never reach null before finding the LCA.
Frequently Asked Questions
Does the order of p and q matter in the input?
No. The conditions check p.val < curr.val && q.val < curr.val, which is commutative. The comparison evaluates correctly regardless of whether p is smaller or larger than q.
What if the BST contains duplicate values? The constraints state that all node values are unique. If duplicates were present, the definition of BST direction splits becomes ambiguous.
What does this problem test in interviews? It tests your ability to identify and exploit the properties of specific data structures (BST ordering rules) to optimize search algorithms.