Skip to content

Commit 7215430

Browse files
committed
feat(datastructures, binary tree): lowest common ancestor
1 parent c09b702 commit 7215430

32 files changed

+525
-75
lines changed

algorithms/arrays/two_sum_less_k/test_two_sum.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
class TwoSumLessKTestCase(unittest.TestCase):
66
def test_1(self):
77
"""numbers = [4,2,11,2,5,3,5,8], target = 7"""
8-
numbers = [4,2,11,2,5,3,5,8]
8+
numbers = [4, 2, 11, 2, 5, 3, 5, 8]
99
target = 7
1010
expected = 6
1111
actual = two_sum_less_than_k(numbers, target)
@@ -21,23 +21,23 @@ def test_2(self):
2121

2222
def test_3(self):
2323
"""numbers = [34,23,1,24,75,33,54,8], k = 60"""
24-
numbers = [34,23,1,24,75,33,54,8]
24+
numbers = [34, 23, 1, 24, 75, 33, 54, 8]
2525
k = 60
2626
expected = 58
2727
actual = two_sum_less_than_k(numbers, k)
2828
self.assertEqual(expected, actual)
2929

3030
def test_4(self):
3131
"""numbers = [5,5,5,5,5,5], k = 15"""
32-
numbers = [5,5,5,5,5,5]
32+
numbers = [5, 5, 5, 5, 5, 5]
3333
k = 15
3434
expected = 10
3535
actual = two_sum_less_than_k(numbers, k)
3636
self.assertEqual(expected, actual)
3737

3838
def test_5(self):
3939
"""numbers = [1,2,3,4,5], k = 3"""
40-
numbers = [1,2,3,4,5]
40+
numbers = [1, 2, 3, 4, 5]
4141
k = 3
4242
expected = -1
4343
actual = two_sum_less_than_k(numbers, k)

algorithms/search/binary_search/maxruntime_n_computers/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ def can_run_for(batteries: List[int], n: int, target_time: int) -> bool:
4949

5050

5151
def max_run_time_2(batteries: List[int], n: int) -> int:
52-
5352
"""
5453
Finds the maximum runtime that can power the computers for the given amount of time.
5554
@@ -67,7 +66,7 @@ def max_run_time_2(batteries: List[int], n: int) -> int:
6766
usable = sum(min(b, mid) for b in batteries)
6867

6968
if usable >= mid * n:
70-
left = mid
69+
left = mid
7170
else:
72-
right = mid - 1
71+
right = mid - 1
7372
return left

algorithms/search/binary_search/maxruntime_n_computers/test_max_runtime.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,38 @@
44

55

66
class MaxRunTimeTestCase(unittest.TestCase):
7-
8-
@parameterized.expand([
9-
([2,3,3,4], 3, 4),
10-
([1,1,4,5], 2, 5),
11-
([2,2,2,2], 1, 8),
12-
([7,2,5,10,8], 2, 16),
13-
([1,2,3,4,5], 2, 7),
14-
([3,4,3,4,5,5,8,2], 4, 8),
15-
([5,2,4], 2, 5),
16-
([1,6,2,6,8], 5, 1)
17-
])
7+
@parameterized.expand(
8+
[
9+
([2, 3, 3, 4], 3, 4),
10+
([1, 1, 4, 5], 2, 5),
11+
([2, 2, 2, 2], 1, 8),
12+
([7, 2, 5, 10, 8], 2, 16),
13+
([1, 2, 3, 4, 5], 2, 7),
14+
([3, 4, 3, 4, 5, 5, 8, 2], 4, 8),
15+
([5, 2, 4], 2, 5),
16+
([1, 6, 2, 6, 8], 5, 1),
17+
]
18+
)
1819
def test_max_runtime_1(self, batteries, n, expected):
1920
actual = max_runtime(batteries, n)
2021
self.assertEqual(expected, actual)
2122

22-
@parameterized.expand([
23-
([2,3,3,4], 3, 4),
24-
([1,1,4,5], 2, 5),
25-
([2,2,2,2], 1, 8),
26-
([7,2,5,10,8], 2, 16),
27-
([1,2,3,4,5], 2, 7),
28-
([3,4,3,4,5,5,8,2], 4, 8),
29-
([5,2,4], 2, 5),
30-
([1,6,2,6,8], 5, 1)
31-
])
23+
@parameterized.expand(
24+
[
25+
([2, 3, 3, 4], 3, 4),
26+
([1, 1, 4, 5], 2, 5),
27+
([2, 2, 2, 2], 1, 8),
28+
([7, 2, 5, 10, 8], 2, 16),
29+
([1, 2, 3, 4, 5], 2, 7),
30+
([3, 4, 3, 4, 5, 5, 8, 2], 4, 8),
31+
([5, 2, 4], 2, 5),
32+
([1, 6, 2, 6, 8], 5, 1),
33+
]
34+
)
3235
def test_max_runtime_2(self, batteries, n, expected):
3336
actual = max_run_time_2(batteries, n)
3437
self.assertEqual(expected, actual)
3538

3639

37-
if __name__ == '__main__':
40+
if __name__ == "__main__":
3841
unittest.main()

datastructures/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
from datastructures.sets import DisjointSetUnion, UnionFind
22

3-
__all__ = [
4-
"DisjointSetUnion",
5-
"UnionFind"
6-
]
3+
__all__ = ["DisjointSetUnion", "UnionFind"]

datastructures/sets/union_find/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def get_count(self) -> int:
4747

4848
class UnionFind:
4949
"""A minimal Union-Find data structure with path compression."""
50-
50+
5151
def __init__(self, size: int):
5252
"""Initializes the data structure with 'size' elements."""
5353
if size <= 0:
@@ -71,4 +71,4 @@ def union(self, x: int, y: int) -> bool:
7171
if root_x != root_y:
7272
self.parent[root_y] = root_x
7373
return True
74-
return False
74+
return False

datastructures/streams/stream_checker/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55

66
class StreamChecker(object):
7-
87
def __init__(self, words: List[str]):
98
"""
109
Initializes a StreamChecker instance.

datastructures/streams/stream_checker/test_stream_checker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ def test_3(self):
3131
self.assertFalse(stream.query("b"))
3232

3333

34-
if __name__ == '__main__':
34+
if __name__ == "__main__":
3535
unittest.main()
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Binary Trees
2+
3+
## Lowest Common Ancestor of a Binary Tree
4+
5+
You are given two nodes, p and q. The task is to return their lowest common ancestor (LCA). Both nodes have a reference
6+
to their parent node. The tree’s root is not provided; you must use the parent pointers to find the nodes’ common
7+
ancestor.
8+
9+
> Note: The lowest common ancestor of two nodes, p and q, is the lowest node in the binary tree, with both p and q as
10+
> descendants. In a tree, a descendant of a node is any node reachable by following edges downward from that node,
11+
> including the node itself.
12+
13+
Constraints
14+
15+
- -10^4 ≤ `node.data` ≤ 10^4
16+
- The number of nodes in the tree is in the range [2, 500]
17+
- All `node.data` are unique
18+
- `p` != `q`
19+
- Both `p` and `q` are present in the tree
20+
21+
### Examples
22+
23+
![Example 1](./images/examples/lowest_common_ancestor_example_1.png)
24+
![Example 2](./images/examples/lowest_common_ancestor_example_2.png)
25+
![Example 3](./images/examples/lowest_common_ancestor_example_3.png)
26+
![Example 4](./images/examples/lowest_common_ancestor_example_4.png)
27+
28+
### Solution
29+
30+
This solution finds the lowest common ancestor (LCA) of two nodes in a binary tree using a smart two-pointer approach.
31+
We start by placing one pointer at node p and the other at node q. Both pointers move up the tree at each step by
32+
following their parent pointers. If a pointer reaches the root (i.e., its parent is None), it jumps to the other
33+
starting node. This process continues until the two pointers meet. The key idea is that by switching starting points
34+
after reaching the top, both pointers end up traveling the same total distance, even if p and q are at different depths.
35+
When they meet, that meeting point is their lowest common ancestor.
36+
37+
The steps of the algorithm are as follows:
38+
39+
1. Initialize two pointers: ptr1 starting at p and ptr2 starting at q.
40+
2. While ptr1 and ptr2 are not pointing to the same node:
41+
- If ptr1 has a parent, move ptr1 to ptr1.parent; otherwise, set ptr1 = q.
42+
- If ptr2 has a parent, move ptr2 to ptr2.parent; otherwise, set ptr2 = p.
43+
44+
3. When ptr1 == ptr2, return ptr1. This node is the lowest common ancestor (LCA) of p and q.
45+
46+
Let’s look at the following illustration to get a better understanding of the solution:
47+
48+
![Solution 1](./images/solutions/lowest_common_ancestor_solution_1.png)
49+
![Solution 2](./images/solutions/lowest_common_ancestor_solution_2.png)
50+
![Solution 3](./images/solutions/lowest_common_ancestor_solution_3.png)
51+
![Solution 4](./images/solutions/lowest_common_ancestor_solution_4.png)
52+
![Solution 5](./images/solutions/lowest_common_ancestor_solution_5.png)
53+
![Solution 6](./images/solutions/lowest_common_ancestor_solution_6.png)
54+
![Solution 7](./images/solutions/lowest_common_ancestor_solution_7.png)
55+
56+
### Complexity
57+
58+
#### Time Complexity
59+
60+
The time complexity is `O(h)` where `h` is the height of the tree, as in the worst case, each pointer might traverse the
61+
entire height of the tree, including `h` steps.
62+
63+
#### Space complexity
64+
65+
The space complexity of this solution is `O(1)` as there is no additional space being used. Only two pointers are being
66+
maintained requiring constant space.
67+
36.4 KB
Loading
36.5 KB
Loading

0 commit comments

Comments
 (0)