235. Lowest Common Ancestor of a Binary Search Tree
Problem Description
You are given a binary search tree (BST) and two nodes p
and q
that exist in the tree. Your task is to find the lowest common ancestor (LCA) of these two nodes.
The lowest common ancestor is defined as the lowest node in the tree that has both p
and q
as descendants. Note that a node can be considered a descendant of itself, meaning if one of the nodes p
or q
is an ancestor of the other, then that ancestor node itself would be the LCA.
In a BST, recall that:
- All values in the left subtree of a node are less than the node's value
- All values in the right subtree of a node are greater than the node's value
- This property holds for every node in the tree
For example, if you have a BST and nodes p
and q
:
- If both
p
andq
values are smaller than the current node's value, their LCA must be in the left subtree - If both
p
andq
values are larger than the current node's value, their LCA must be in the right subtree - If one value is smaller and one is larger than the current node (or one equals the current node), then the current node is the LCA
The solution leverages the BST property to efficiently find the LCA by comparing the values of p
and q
with the current node and deciding which direction to traverse, avoiding the need to search the entire tree.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: A tree is a special type of graph (specifically, a connected acyclic graph). The BST in this problem has nodes connected by edges (parent-child relationships).
Is it a tree?
- Yes: The problem explicitly states we're working with a Binary Search Tree (BST), which is a tree data structure.
DFS
- We arrive at DFS: Since we're dealing with a tree structure, the flowchart leads us to use Depth-First Search (DFS).
Conclusion: The flowchart suggests using a DFS approach for finding the Lowest Common Ancestor in a Binary Search Tree.
Additional Context: While the flowchart correctly identifies DFS as the pattern, the actual solution leverages the BST property to optimize the traversal. Instead of exploring all paths, we can make intelligent decisions at each node:
- If both
p
andq
values are less than the current node's value, we traverse left - If both
p
andq
values are greater than the current node's value, we traverse right - Otherwise, we've found the LCA
This is essentially a modified DFS where the BST property guides our traversal direction, making it more efficient than a standard DFS that would explore all branches.
Intuition
The key insight comes from understanding the BST property: for any node, all values in its left subtree are smaller, and all values in its right subtree are larger.
Let's think about where the LCA could be located. If we're standing at any node in the tree, there are only three possibilities for nodes p
and q
:
- Both
p
andq
are in the left subtree (both values < current node's value) - Both
p
andq
are in the right subtree (both values > current node's value) p
andq
are split across different subtrees, or one of them is the current node itself
The magic happens when we realize that in case 3, the current node MUST be the LCA! Why? Because if p
and q
are on different sides of the current node (or one equals the current node), then the current node is the first point where the paths from the root to p
and to q
diverge - which is exactly the definition of the lowest common ancestor.
This observation leads to a simple strategy: start at the root and keep moving towards the subtree that contains both nodes. The moment we find a node where p
and q
would go in different directions (or we're at p
or q
itself), we've found our LCA.
The beauty of this approach is that we don't need to search the entire tree or even keep track of paths. We can make a decision at each node based solely on comparing values: if current.val < min(p.val, q.val)
, go right; if current.val > max(p.val, q.val)
, go left; otherwise, we've found the LCA.
This turns what could be a complex tree traversal problem into a simple value comparison problem, taking advantage of the ordered nature of the BST.
Solution Approach
The implementation uses an iterative approach to traverse the BST, making decisions at each node based on value comparisons.
Algorithm Steps:
- Start with
root
as the current node - Enter an infinite loop that continues until we find the LCA
- At each iteration, compare the current node's value with both
p.val
andq.val
:- If
root.val < min(p.val, q.val)
: Both nodes are in the right subtree, so move toroot.right
- If
root.val > max(p.val, q.val)
: Both nodes are in the left subtree, so move toroot.left
- Otherwise: We've found the LCA, return
root
- If
Key Implementation Details:
The solution uses a while 1
(infinite loop) structure that only exits when we return the LCA. This is guaranteed to terminate because:
- We're given that both
p
andq
exist in the tree - Each iteration moves us closer to the LCA
- The condition in the
else
clause will eventually be met
Why This Works:
When root.val
is between p.val
and q.val
(inclusive), or equals one of them, we know that:
- If we go left, we'll lose access to the larger-valued node
- If we go right, we'll lose access to the smaller-valued node
- Therefore, the current node is the lowest point that can reach both nodes
Time and Space Complexity:
- Time Complexity:
O(h)
whereh
is the height of the tree. In the worst case (skewed tree), this could beO(n)
, but for a balanced BST, it'sO(log n)
. - Space Complexity:
O(1)
as we only use a constant amount of extra space (just moving theroot
pointer).
The iterative approach avoids the recursive call stack, making it more memory-efficient than a recursive solution while maintaining the same time complexity.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through finding the LCA of nodes with values 2 and 8 in this BST:
6 / \ 2 8 / \ / \ 0 4 7 9 / \ 3 5
Step-by-step execution:
- Start at root (6)
p.val = 2
,q.val = 8
- Check: Is 6 < both 2 and 8? No (6 is not less than 2)
- Check: Is 6 > both 2 and 8? No (6 is not greater than 8)
- Since 6 is between 2 and 8, node 6 is the LCA
- Return node 6
Let's try another example with nodes 2 and 4:
6 / \ 2 8 / \ / \ 0 4 7 9 / \ 3 5
-
Start at root (6)
p.val = 2
,q.val = 4
- Check: Is 6 > both 2 and 4? Yes!
- Move to left child:
root = root.left
(node 2)
-
Current node (2)
- Check: Is 2 < both 2 and 4? No (2 equals 2)
- Check: Is 2 > both 2 and 4? No (2 is not greater than 4)
- Since one node equals the current node, node 2 is the LCA
- Return node 2
One more example with nodes 3 and 5:
6 / \ 2 8 / \ / \ 0 4 7 9 / \ 3 5
-
Start at root (6)
p.val = 3
,q.val = 5
- Check: Is 6 > both 3 and 5? Yes!
- Move to left child:
root = root.left
(node 2)
-
Current node (2)
- Check: Is 2 < both 3 and 5? Yes!
- Move to right child:
root = root.right
(node 4)
-
Current node (4)
- Check: Is 4 < both 3 and 5? No (4 is between 3 and 5)
- Check: Is 4 > both 3 and 5? No (4 is between 3 and 5)
- Since 4 is between 3 and 5, node 4 is the LCA
- Return node 4
The algorithm efficiently navigates down the tree, making exactly the right turns based on value comparisons, never needing to backtrack or search multiple paths.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, x):
4# self.val = x
5# self.left = None
6# self.right = None
7
8
9class Solution:
10 def lowestCommonAncestor(
11 self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode'
12 ) -> 'TreeNode':
13 """
14 Find the lowest common ancestor (LCA) of two nodes in a binary search tree.
15
16 Args:
17 root: The root node of the BST
18 p: First target node
19 q: Second target node
20
21 Returns:
22 The lowest common ancestor node of p and q
23 """
24 # Iterate through the tree starting from root
25 while True:
26 # If both nodes are smaller than current node, LCA must be in left subtree
27 if root.val < min(p.val, q.val):
28 root = root.right
29 # If both nodes are greater than current node, LCA must be in right subtree
30 elif root.val > max(p.val, q.val):
31 root = root.left
32 # Current node is the LCA when:
33 # - One node is on left and other is on right of current node, or
34 # - Current node equals one of the target nodes
35 else:
36 return root
37
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode(int x) { val = x; }
8 * }
9 */
10
11class Solution {
12 /**
13 * Finds the lowest common ancestor (LCA) of two nodes in a Binary Search Tree.
14 * Utilizes the BST property where left subtree values < root value < right subtree values.
15 *
16 * @param root The root node of the BST
17 * @param p First target node
18 * @param q Second target node
19 * @return The lowest common ancestor node of p and q
20 */
21 public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
22 // Find the minimum and maximum values between p and q for comparison
23 int minValue = Math.min(p.val, q.val);
24 int maxValue = Math.max(p.val, q.val);
25
26 // Traverse the tree iteratively
27 while (true) {
28 // If current node value is less than both p and q,
29 // LCA must be in the right subtree
30 if (root.val < minValue) {
31 root = root.right;
32 }
33 // If current node value is greater than both p and q,
34 // LCA must be in the left subtree
35 else if (root.val > maxValue) {
36 root = root.left;
37 }
38 // Current node is between p and q (inclusive),
39 // so it must be the LCA
40 else {
41 return root;
42 }
43 }
44 }
45}
46
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode(int x) : val(x), left(NULL), right(NULL) {}
8 * };
9 */
10
11class Solution {
12public:
13 /**
14 * Finds the lowest common ancestor (LCA) of two nodes in a Binary Search Tree.
15 *
16 * @param root - The root node of the BST
17 * @param p - First target node
18 * @param q - Second target node
19 * @return The lowest common ancestor node of p and q
20 *
21 * Algorithm:
22 * - Utilizes BST property: left subtree values < root value < right subtree values
23 * - If both p and q values are less than current node, LCA must be in left subtree
24 * - If both p and q values are greater than current node, LCA must be in right subtree
25 * - Otherwise, current node is the LCA (split point or one of the nodes itself)
26 */
27 TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
28 // Iterative approach to find LCA
29 while (true) {
30 // Both nodes are in the right subtree
31 if (root->val < std::min(p->val, q->val)) {
32 root = root->right;
33 }
34 // Both nodes are in the left subtree
35 else if (root->val > std::max(p->val, q->val)) {
36 root = root->left;
37 }
38 // Current node is the LCA (split point or equal to p or q)
39 else {
40 return root;
41 }
42 }
43 }
44};
45
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15/**
16 * Finds the lowest common ancestor (LCA) of two nodes in a Binary Search Tree (BST).
17 *
18 * The LCA is defined as the lowest node in the tree that has both p and q as descendants,
19 * where we allow a node to be a descendant of itself.
20 *
21 * @param root - The root node of the binary search tree
22 * @param p - The first target node to find the LCA for
23 * @param q - The second target node to find the LCA for
24 * @returns The lowest common ancestor node of p and q, or null if not found
25 *
26 * Time Complexity: O(h) where h is the height of the tree
27 * Space Complexity: O(1) - iterative approach uses constant extra space
28 */
29function lowestCommonAncestor(
30 root: TreeNode | null,
31 p: TreeNode | null,
32 q: TreeNode | null,
33): TreeNode | null {
34 // Traverse the tree starting from the root
35 while (root) {
36 // If both p and q are smaller than root, LCA must be in the left subtree
37 if (root.val > p!.val && root.val > q!.val) {
38 root = root.left;
39 }
40 // If both p and q are greater than root, LCA must be in the right subtree
41 else if (root.val < p!.val && root.val < q!.val) {
42 root = root.right;
43 }
44 // Otherwise, we've found the split point (LCA)
45 // This happens when:
46 // 1. p and q are on different sides of root, or
47 // 2. root equals either p or q
48 else {
49 return root;
50 }
51 }
52
53 // Return null if tree is empty or LCA not found
54 return null;
55}
56
Time and Space Complexity
The time complexity is O(h)
where h
is the height of the binary search tree. In the worst case when the tree is skewed (essentially a linked list), h = n
where n
is the number of nodes, making the time complexity O(n)
. In the best case of a balanced BST, the time complexity would be O(log n)
.
The space complexity is O(1)
as the algorithm uses only a constant amount of extra space. The iterative approach with a while loop traverses the tree by updating the root
pointer without using recursion or any additional data structures, requiring only a fixed amount of memory regardless of the input size.
Common Pitfalls
1. Incorrect Direction Logic
One of the most common mistakes is reversing the traversal direction logic. When both values are smaller than the current node, developers often incorrectly move left instead of right.
Incorrect Implementation:
if root.val < min(p.val, q.val):
root = root.left # Wrong! Should go right
elif root.val > max(p.val, q.val):
root = root.right # Wrong! Should go left
Why This Happens: The confusion arises from thinking about where the values are located rather than where we need to move. If root.val < min(p.val, q.val)
, both p and q have larger values than root, so they must be in the right subtree.
Correct Implementation:
if root.val < min(p.val, q.val):
root = root.right # Both nodes are greater, go right
elif root.val > max(p.val, q.val):
root = root.left # Both nodes are smaller, go left
2. Not Handling Edge Cases with Node Values
Another pitfall is not properly handling the case where one of the target nodes IS the LCA itself. Some implementations try to exclude equality checks.
Problematic Approach:
if p.val < root.val and q.val < root.val: root = root.left elif p.val > root.val and q.val > root.val: root = root.right else: return root
This works but using min()
and max()
with inclusive comparisons is cleaner and handles all cases including when p.val == root.val
or q.val == root.val
.
3. Assuming p.val < q.val
Don't assume any ordering between p and q values. The solution should work regardless of whether p's value is smaller or larger than q's value. Using min()
and max()
ensures the logic works for both cases.
Memory Tip: Think of it as "chasing" both nodes - if the current value is too small for both targets, move toward larger values (right); if too large for both, move toward smaller values (left).
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Binary Search Tree Intro Definition A Binary Search Tree or BST is a rooted binary tree with the value of each internal node being greater than all the values in the respective node's left subtree and less than the ones in its right subtree An empty tree is a BST since it satisfies the
Want a Structured Path to Master System Design Too? Don’t Miss This!