Skip to content

Commit b3849a9

Browse files
committed
Lowest Common Ancestor of a Binary Search Tree
1 parent c592d73 commit b3849a9

File tree

4 files changed

+179
-0
lines changed

4 files changed

+179
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
| 0219 | Easy | Contains Duplicate II | Array | [solution](./docs/0219-Contains-Duplicate-II.md) |
4848
| 0226 | Easy | Invert Binary Tree | Tree, Depth-First Search, Breadth-First Search, Binary Tree | [solution](./docs/0226-Invert-Binary-Tree.md) |
4949
| 0230 | Medium | Kth Smallest Element in a BST | Tree, Depth-First Search, Binary Search Tree, Binary Tree | [solution](./docs/0230-Kth-Smallest-Element-in-a-BST.md) |
50+
| 0236 | Medium | Lowest Common Ancestor of a Binary Tree | Tree, Depth-First Search, Binary Tree | [solution](docs/0236-Lowest-Common-Ancestor-of-a-Binary-Tree.md) |
5051
| 0238 | Medium | Product of Array Except Self | Array | [solution](./docs/0238-Product-Of-Array-Except-Self.md) |
5152
| 0239 | Hard | Sliding Window Maximum | Array, Queue, Sliding Window, Heap (Priority Queue), Monotonic Queue | [solution](./docs/0239-Sliding-Window-Maximum.md) |
5253
| 0242 | Easy | Valid Anagram | String, HashTable | [solution](./docs/0242-Valid-Anagram.md) |
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# 🧠 [Lowest Common Ancestor of a Binary Search Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/)
2+
3+
## 📘 Problem Statement
4+
5+
Given a **binary search tree (BST)**, find the **lowest common ancestor (LCA)** of two given nodes `p` and `q` in the
6+
tree.
7+
8+
According to the definition of LCA on Wikipedia:
9+
> “The lowest common ancestor is defined between two nodes `p` and `q` as the lowest node in `T` that has both `p` and
10+
`q` as descendants (where we allow a node to be a descendant of itself).”
11+
12+
---
13+
14+
## 💡 Intuition
15+
16+
In a **Binary Search Tree (BST)**:
17+
18+
- All nodes in the left subtree of a node have smaller values.
19+
- All nodes in the right subtree have larger values.
20+
21+
This structure allows a comparison-based traversal from the root to the correct ancestor.
22+
The first node encountered where the path to `p` and `q` **splits** is the **lowest common ancestor**.
23+
24+
---
25+
26+
## 🔍 Approach
27+
28+
The algorithm leverages the ordered nature of a BST:
29+
30+
1. Start at the root node.
31+
2. At each step:
32+
- If both `p` and `q` are less than the current node, LCA lies in the **left subtree**.
33+
- If both `p` and `q` are greater than the current node, LCA lies in the **right subtree**.
34+
- Otherwise, the current node is the **lowest common ancestor**.
35+
3. Stop the traversal once this condition is met.
36+
37+
Edge cases:
38+
39+
- If any of `p`, `q`, or `root` is `null`, it throws an `IllegalArgumentException`.
40+
- If either `p` or `q` is `null`, the other node is returned, following the descendant-of-itself rule.
41+
42+
---
43+
44+
## ⏱️ Complexity
45+
46+
| Metric | Complexity |
47+
|--------|----------------------------------------------------------|
48+
| Time | `O(h)`, where *h* is the height of the BST |
49+
| Space | `O(1)`, iterative traversal with no additional memory used |
50+
51+
---
52+
53+
## 🧪 Code
54+
55+
- [Java](../src/main/java/io/dksifoua/leetcode/lowestcommonancestorofabinarysearchtree/Solution.java)
56+
57+
---
58+
59+
## ✅ Summary
60+
61+
This solution takes advantage of the binary search tree's sorted properties to efficiently locate the lowest common
62+
ancestor without needing to traverse the entire tree. By narrowing the search space based on comparisons, it ensures an
63+
optimal `O(h)` runtime. This approach is simple, elegant, and scalable for large trees.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.dksifoua.leetcode.lowestcommonancestorofabinarysearchtree;
2+
3+
import io.dksifoua.leetcode.utils.TreeNode;
4+
5+
public class Solution {
6+
7+
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
8+
if (root == null || (p == null && q == null)) throw new IllegalArgumentException();
9+
if (p == null) return q;
10+
if (q == null) return p;
11+
12+
TreeNode current = root;
13+
while (current != null) {
14+
if ((p.getValue() < current.getValue() && current.getValue() < q.getValue())
15+
|| (q.getValue() < current.getValue() && current.getValue() < p.getValue())) {
16+
return current;
17+
}
18+
19+
if (p.getValue() < current.getValue() && q.getValue() < current.getValue()) {
20+
current = current.getLeft();
21+
continue;
22+
}
23+
24+
if (p.getValue() > current.getValue() && q.getValue() > current.getValue()) {
25+
current = current.getRight();
26+
continue;
27+
}
28+
29+
break;
30+
}
31+
32+
return current;
33+
}
34+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package io.dksifoua.leetcode.lowestcommonancestorofabinarysearchtree;
2+
3+
import io.dksifoua.leetcode.utils.TreeNode;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.junit.jupiter.api.Assertions.*;
7+
8+
class SolutionTest {
9+
10+
private final Solution solution = new Solution();
11+
12+
@Test
13+
void testLowestCommonAncestor_basicCase() {
14+
TreeNode root = TreeNode.build(new Integer[] { 6, 2, 8, 0, 4, 7, 9, null, null, 3, 5 });
15+
TreeNode p = findNode(root, 2);
16+
TreeNode q = findNode(root, 8);
17+
TreeNode expected = findNode(root, 6);
18+
19+
TreeNode result = solution.lowestCommonAncestor(root, p, q);
20+
21+
assertEquals(expected.getValue(), result.getValue());
22+
}
23+
24+
@Test
25+
void testLowestCommonAncestor_deepLeftSubtree() {
26+
TreeNode root = TreeNode.build(new Integer[] { 6, 2, 8, 0, 4, 7, 9, null, null, 3, 5 });
27+
TreeNode p = findNode(root, 2);
28+
TreeNode q = findNode(root, 4);
29+
TreeNode expected = findNode(root, 2);
30+
31+
TreeNode result = solution.lowestCommonAncestor(root, p, q);
32+
33+
assertEquals(expected.getValue(), result.getValue());
34+
}
35+
36+
@Test
37+
void testLowestCommonAncestor_sameNode() {
38+
TreeNode root = TreeNode.build(new Integer[] { 6, 2, 8, 0, 4, 7, 9 });
39+
TreeNode p = findNode(root, 2);
40+
TreeNode result = solution.lowestCommonAncestor(root, p, p);
41+
42+
assertEquals(p.getValue(), result.getValue());
43+
}
44+
45+
@Test
46+
void testLowestCommonAncestor_pIsNull() {
47+
TreeNode root = TreeNode.build(new Integer[] { 6, 2, 8 });
48+
TreeNode q = findNode(root, 2);
49+
50+
assertEquals(q.getValue(), solution.lowestCommonAncestor(root, null, q).getValue());
51+
}
52+
53+
@Test
54+
void testLowestCommonAncestor_qIsNull() {
55+
TreeNode root = TreeNode.build(new Integer[] { 6, 2, 8 });
56+
TreeNode p = findNode(root, 8);
57+
58+
assertEquals(p.getValue(), solution.lowestCommonAncestor(root, p, null).getValue());
59+
}
60+
61+
@Test
62+
void testLowestCommonAncestor_rootIsNull() {
63+
TreeNode p = new TreeNode(1, null, null);
64+
TreeNode q = new TreeNode(2, null, null);
65+
66+
assertThrows(IllegalArgumentException.class, () -> solution.lowestCommonAncestor(null, p, q));
67+
}
68+
69+
@Test
70+
void testLowestCommonAncestor_bothPQNull() {
71+
TreeNode root = new TreeNode(5, null, null);
72+
assertThrows(IllegalArgumentException.class, () -> solution.lowestCommonAncestor(root, null, null));
73+
}
74+
75+
private TreeNode findNode(TreeNode root, int value) {
76+
if (root == null) return null;
77+
if (root.getValue() == value) return root;
78+
TreeNode left = findNode(root.getLeft(), value);
79+
return (left != null) ? left : findNode(root.getRight(), value);
80+
}
81+
}

0 commit comments

Comments
 (0)