Hello! Welcome back to our series on Binary Search Trees.
In our last lesson, we saw how the sorted nature of an in-order traversal allows for an elegant solution to finding the k-th smallest element. Today, we'll continue this theme of leveraging the unique properties of a BST to solve another classic interview problem: finding the Lowest Common Ancestor (LCA) of two nodes.
The goal of this lesson is to understand the efficient algorithm for finding the LCA in a BST and be able to implement it. This is a very common question because it tests your understanding of the core BST invariant and your ability to craft a solution that is significantly better than what's possible in a general binary tree.
What is a Lowest Common Ancestor?
The Lowest Common Ancestor (LCA) between two nodes, p and q, in a tree is defined as the lowest (i.e., deepest) node that has both p and q as descendants. A node can be a descendant of itself.
Let's look at a visual example.

For a general binary tree, a common approach is to find the path from the root to p and the path from the root to q, and then find the last common node in both paths. This works, but it's inefficient, requiring time and space in the worst case. For a BST, we can do much better.
The Key Insight: Leveraging the BST Property
The defining property of a BST is that for any given node, all values in its left subtree are smaller, and all values in its right subtree are larger. We can use this to our advantage.
Imagine you are at the root of the tree, looking for the LCA of p and q. There are three possibilities:
- Both
pandqhave values smaller than the current node's value. This means the LCA must be in the left subtree. - Both
pandqhave values larger than the current node's value. This means the LCA must be in the right subtree. - The values of
pandqsplit at the current node: one is smaller (or equal) and one is larger (or equal). This means the current node is the "fork in the road." It's the first ancestor from the top that haspandqin different subtrees. Therefore, the current node is the LCA. This case also handles the situation where one of the target nodes is the ancestor of the other.
This logic allows us to discard half of the tree at each step, similar to binary search.
To see this logic in action, let's watch a short video that explains it visually.
Lowest Common Ancestor of a Binary Search Tree - Leetcode 235 - Python
This video from NeetCode explains the core logic behind finding the LCA in a BST by identifying the 'split point'. Pay close attention to the three cases he describes.
Watch from 00:46 to 04:44. This segment defines the LCA, explains how the split point works, and covers the edge case where one node is an ancestor of the other.
The Algorithm: Iterative Approach
As the video demonstrates, this logic translates into a highly efficient iterative algorithm. This approach is generally preferred in interviews because it uses constant extra space.
Here is the algorithm:
- Start with a pointer,
current, at therootof the tree. - Loop as long as
currentis notnull. - If both
p.valandq.valare greater thancurrent.val, move to the right child:current = current.right. - If both
p.valandq.valare less thancurrent.val, move to the left child:current = current.left. - If neither of the above is true, it means we've found the split point (or one of the nodes is the LCA). The
currentnode is the LCA, so we return it.
Let's see the code implementation of this.
Lowest Common Ancestor of a Binary Search Tree - Leetcode 235 - Python
Now, let's see how this logic translates into a clean, iterative Python solution. This is the implementation you'd want to aim for in an interview.
Watch the code walkthrough from 05:10 to 06:32. Notice how the simple while loop directly implements the three cases we just discussed.
Here is the Python code for your reference. It's a pattern worth committing to memory.
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
current = root
while current:
# If both p and q are greater than parent
if p.val > current.val and q.val > current.val:
current = current.right
# If both p and q are smaller than parent
elif p.val < current.val and q.val < current.val:
current = current.left
# We have found the split point, i.e. the LCA
else:
return current
Test your understanding!
Consider the BST from the image above (the one on the right, rooted at 8). If you are looking for the LCA of nodes p=10 and q=14, trace the path of the current pointer using the iterative algorithm. What nodes does it visit?
Show answer
currentstarts atroot(node8).p.val(10) >current.val(8) andq.val(14) >current.val(8). Both are larger. So,currentmoves tocurrent.right(node12).- Now
currentis at node12. p.val(10) <current.val(12) andq.val(14) >current.val(12). This is a split! Theelseblock is executed.- The algorithm returns
current, which is node12.
The visited nodes are 8 and 12. The LCA is 12.
Alternative: Recursive Approach
The same logic can be implemented recursively. The code is often more concise, but it comes at the cost of using extra space on the call stack.
Lowest Common Ancestor in a Binary Search Tree
For completeness, let's look at the recursive solution. The logic is identical but expressed through recursive calls. The article 'Lowest Common Ancestor in a Binary Search Tree' from GeeksforGeeks provides a clear implementation.
Please read the section '[Better Approach] Using BST Properties (Recursive Approach)'. Focus on the Python code provided to see the recursive structure and how it mirrors the iterative logic.
Here's the recursive Python code from the article:
def LCA(root, n1, n2):
if root is None:
return None
# If both n1 and n2 are smaller than root, LCA lies in left
if root.data > n1.data and root.data > n2.data:
return LCA(root.left, n1, n2)
# If both n1 and n2 are greater than root, LCA lies in right
if root.data < n1.data and root.data < n2.data:
return LCA(root.right, n1, n2)
# If we reach here, then root is the LCA
return root
Complexity Analysis
Let's quickly summarize the performance, which is the main reason this approach is so powerful.
- Time Complexity: , where is the height of the tree. At each level, we make one decision and eliminate the rest of the tree at that level. For a balanced BST, this becomes . In the worst case (a skewed tree), it's .
- Space Complexity:
- Iterative: — We only use a few pointers, regardless of the tree's size. This is optimal.
- Recursive: — Due to the recursion call stack. For a balanced BST, this is ; for a skewed tree, .
This is a significant improvement over the time and space complexity of the naive approach for a general binary tree.
Conclusion
In this lesson, we tackled another fundamental BST problem, perfect for technical interviews. You've learned how to exploit the BST ordering property to find the Lowest Common Ancestor efficiently.
Key Takeaways:
- The LCA in a BST is the "split point" node, where the paths to the two target nodes diverge.
- The search for the LCA can be done in time by traversing from the root and deciding at each node whether to go left, go right, or stop.
- The iterative solution is generally preferred due to its optimal space complexity.
- Be prepared to explain why this approach is superior to methods used for general binary trees.
In our next and final lesson on BSTs, we'll address another common problem: constructing a height-balanced BST from a sorted array. This will tie together concepts of tree balance and BST properties. See you then