Skip to content

Commit d0334be

Browse files
committed
feat(data structures, puzzles, strings): longest common prefix, number of islands
1 parent 56e8dff commit d0334be

26 files changed

+4596
-119
lines changed

datastructures/graphs/disjoint_set/__init__.py

Whitespace-only changes.
Lines changed: 3 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,5 @@
1-
class DisjointSetUnion:
2-
"""A class for the Union-Find (Disjoint Set Union) data structure."""
1+
from datastructures.sets.union_find.disjoint_set_union import DisjointSetUnion
2+
from datastructures.sets.union_find.union_find import UnionFind
33

4-
def __init__(self, size: int):
5-
"""Initializes the data structure with 'size' elements, each in its own set."""
6-
if size <= 0:
7-
raise ValueError("Size must be a positive integer.")
8-
self.root = list(range(size))
9-
self.rank = [1] * size # For union by rank
10-
self.count = size # Number of disjoint sets
114

12-
def find(self, i: int) -> int:
13-
"""Finds the representative (root) of the set containing element 'i'."""
14-
if self.root[i] == i:
15-
return i
16-
# Path compression: make all nodes on the path point to the root
17-
self.root[i] = self.find(self.root[i])
18-
return self.root[i]
19-
20-
def union(self, i: int, j: int) -> bool:
21-
"""
22-
Merges the sets containing elements 'i' and 'j'.
23-
Returns True if a merge occurred, False if they were already in the same set.
24-
"""
25-
root_i = self.find(i)
26-
root_j = self.find(j)
27-
28-
if root_i != root_j:
29-
# Union by rank: attach the smaller tree to the larger tree
30-
if self.rank[root_i] > self.rank[root_j]:
31-
self.root[root_j] = root_i
32-
elif self.rank[root_i] < self.rank[root_j]:
33-
self.root[root_i] = root_j
34-
else:
35-
self.root[root_j] = root_i
36-
self.rank[root_i] += 1
37-
38-
self.count -= 1
39-
return True
40-
41-
return False
42-
43-
def get_count(self) -> int:
44-
"""Returns the current number of disjoint sets."""
45-
return self.count
46-
47-
48-
class UnionFind:
49-
"""A minimal Union-Find data structure with path compression."""
50-
51-
def __init__(self, size: int):
52-
"""Initializes the data structure with 'size' elements."""
53-
if size <= 0:
54-
raise ValueError("Size must be a positive integer.")
55-
self.parent = list(range(size))
56-
57-
def find(self, x: int) -> int:
58-
"""Finds the representative (root) of the set containing element 'x'."""
59-
if self.parent[x] != x:
60-
# Path compression
61-
self.parent[x] = self.find(self.parent[x])
62-
return self.parent[x]
63-
64-
def union(self, x: int, y: int) -> bool:
65-
"""
66-
Merges the sets containing elements 'x' and 'y'.
67-
Returns True if a merge occurred, False if already in same set.
68-
"""
69-
root_x = self.find(x)
70-
root_y = self.find(y)
71-
if root_x != root_y:
72-
self.parent[root_y] = root_x
73-
return True
74-
return False
5+
__all__ = ["UnionFind", "DisjointSetUnion"]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
class DisjointSetUnion:
2+
"""A class for the Union-Find (Disjoint Set Union) data structure."""
3+
4+
def __init__(self, size: int):
5+
"""Initializes the data structure with 'size' elements, each in its own set."""
6+
if size <= 0:
7+
raise ValueError("Size must be a positive integer.")
8+
self.root = list(range(size))
9+
self.rank = [1] * size # For union by rank
10+
self.count = size # Number of disjoint sets
11+
12+
def find(self, i: int) -> int:
13+
"""Finds the representative (root) of the set containing element 'i'."""
14+
if self.root[i] == i:
15+
return i
16+
# Path compression: make all nodes on the path point to the root
17+
self.root[i] = self.find(self.root[i])
18+
return self.root[i]
19+
20+
def union(self, i: int, j: int) -> bool:
21+
"""
22+
Merges the sets containing elements 'i' and 'j'.
23+
Returns True if a merge occurred, False if they were already in the same set.
24+
"""
25+
root_i = self.find(i)
26+
root_j = self.find(j)
27+
28+
if root_i != root_j:
29+
# Union by rank: attach the smaller tree to the larger tree
30+
if self.rank[root_i] > self.rank[root_j]:
31+
self.root[root_j] = root_i
32+
elif self.rank[root_i] < self.rank[root_j]:
33+
self.root[root_i] = root_j
34+
else:
35+
self.root[root_j] = root_i
36+
self.rank[root_i] += 1
37+
38+
self.count -= 1
39+
return True
40+
41+
return False
42+
43+
def get_count(self) -> int:
44+
"""Returns the current number of disjoint sets."""
45+
return self.count
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class UnionFind:
2+
"""A minimal Union-Find data structure with path compression."""
3+
4+
def __init__(self, size: int):
5+
"""Initializes the data structure with 'size' elements."""
6+
if size <= 0:
7+
raise ValueError("Size must be a positive integer.")
8+
self.parent = list(range(size))
9+
10+
def find(self, x: int) -> int:
11+
"""Finds the representative (root) of the set containing element 'x'."""
12+
if self.parent[x] != x:
13+
# Path compression
14+
self.parent[x] = self.find(self.parent[x])
15+
return self.parent[x]
16+
17+
def union(self, x: int, y: int) -> bool:
18+
"""
19+
Merges the sets containing elements 'x' and 'y'.
20+
Returns True if a merge occurred, False if already in same set.
21+
"""
22+
root_x = self.find(x)
23+
root_y = self.find(y)
24+
if root_x != root_y:
25+
self.parent[root_y] = root_x
26+
return True
27+
return False

datastructures/trees/trie/trie.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ def insert(self, word: str, index: Optional[int] = None) -> None:
2929
3030
e(2) → r(2)
3131
32-
Index then tracks the earliest index of the word in the original list. So, for the example above, the index of
33-
"player" would be 2, not 0.
32+
The Index then tracks the earliest index of the word in the original list. So, for the example above, the index
33+
of "player" would be 2, not 0.
3434
3535
Parameters:
3636
word (str): The word to insert

puzzles/arrays/lucky_numbers_in_a_matrix/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ def lucky_numbers_simulation(matrix: List[List[int]]) -> List[int]:
7878

7979
row_min = []
8080
for i in range(row_length):
81-
r_min = float('inf')
81+
r_min = float("inf")
8282
for j in range(col_length):
8383
r_min = min(r_min, matrix[i][j])
8484
row_min.append(r_min)
8585

8686
col_max = []
8787
for i in range(col_length):
88-
c_max = float('-inf')
88+
c_max = float("-inf")
8989
for j in range(row_length):
9090
c_max = max(c_max, matrix[j][i])
9191
col_max.append(c_max)

puzzles/arrays/lucky_numbers_in_a_matrix/test_lucky_numbers_in_a_matrix.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import unittest
22
from typing import List
33
from parameterized import parameterized
4-
from puzzles.arrays.lucky_numbers_in_a_matrix import lucky_numbers, lucky_numbers_simulation
4+
from puzzles.arrays.lucky_numbers_in_a_matrix import (
5+
lucky_numbers,
6+
lucky_numbers_simulation,
7+
)
58

69

710
class LuckyNumbersInAMatrixTestCase(unittest.TestCase):
@@ -27,8 +30,8 @@ class LuckyNumbersInAMatrixTestCase(unittest.TestCase):
2730
([[30, 20, 10], [40, 50, 60], [70, 80, 90]], [70]),
2831
([[5, 1, 9], [10, 8, 2], [7, 3, 6]], []),
2932
([[22, 11], [88, 77], [55, 44]], [77]),
30-
([[1,10,4,2],[9,3,8,7],[15,16,17,12]], [12]),
31-
([[7,8],[1,2]], [7]),
33+
([[1, 10, 4, 2], [9, 3, 8, 7], [15, 16, 17, 12]], [12]),
34+
([[7, 8], [1, 2]], [7]),
3235
]
3336
)
3437
def test_lucky_numbers_in_matrix(
@@ -58,8 +61,8 @@ def test_lucky_numbers_in_matrix(
5861
([[30, 20, 10], [40, 50, 60], [70, 80, 90]], [70]),
5962
([[5, 1, 9], [10, 8, 2], [7, 3, 6]], []),
6063
([[22, 11], [88, 77], [55, 44]], [77]),
61-
([[1,10,4,2],[9,3,8,7],[15,16,17,12]], [12]),
62-
([[7,8],[1,2]], [7]),
64+
([[1, 10, 4, 2], [9, 3, 8, 7], [15, 16, 17, 12]], [12]),
65+
([[7, 8], [1, 2]], [7]),
6366
]
6467
)
6568
def test_lucky_numbers_in_matrix_simulation(

puzzles/graphs/number_of_islands/README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
# Number of Islands
22

3-
Given an m x n 2D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands.
3+
Given an m x n 2D binary grid which represents a map of '1's (land) and '0's (water), return the number of islands.
44

5-
An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume
6-
all four edges of the grid are all surrounded by water.
5+
An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically.
6+
If any '1' cells are connected to each other horizontally or vertically (not diagonally), they form an island
7+
You may assume all four edges of the grid are all surrounded by water.
8+
9+
Constraints
10+
11+
- 1 <= `grid.length` <= 50
12+
- 1 <= `grid[i].length` <= 50
13+
- `grid[i][j]` is either '0' or '1'
14+
15+
## Examples
716

817
Example 1:
918

@@ -14,6 +23,7 @@ Input: grid = [
1423
["0","0","0","0","0"]
1524
]
1625
Output: 1
26+
1727
Example 2:
1828

1929
Input: grid = [
Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import List
1+
from typing import List, Tuple, Set
2+
from puzzles.graphs.number_of_islands.union_find import UnionFind
23

34

45
def num_of_islands(grid: List[List[str]]) -> int:
@@ -7,26 +8,31 @@ def num_of_islands(grid: List[List[str]]) -> int:
78
if len(grid) == 0 or not grid[0]:
89
return islands
910

10-
visited = set()
11+
visited: Set[Tuple[int, int]] = set()
1112
number_of_rows, number_of_cols = len(grid), len(grid[0])
1213

13-
def dfs(r: int, c: int):
14-
if (
15-
r not in range(number_of_rows)
16-
or c not in range(number_of_cols)
17-
or grid[r][c] == "0"
18-
or (r, c) in visited
19-
):
20-
return
21-
22-
visited.add((r, c))
23-
# [0, 1] -> move right along column
24-
# [0, -1] -> move left along column
25-
# [1, 0] -> move down along row
26-
# [-1, 0] -> move up along row
27-
directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]
28-
for dr, dc in directions:
29-
dfs(r + dr, c + dc)
14+
def dfs(start_r: int, start_c: int):
15+
stack = [(start_r, start_c)]
16+
17+
while stack:
18+
r, c = stack.pop()
19+
20+
if (
21+
r not in range(number_of_rows)
22+
or c not in range(number_of_cols)
23+
or grid[r][c] == "0"
24+
or (r, c) in visited
25+
):
26+
continue
27+
28+
visited.add((r, c))
29+
# [0, 1] -> move right along column
30+
# [0, -1] -> move left along column
31+
# [1, 0] -> move down along row
32+
# [-1, 0] -> move up along row
33+
directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]
34+
for dr, dc in directions:
35+
stack.append((r + dr, c + dc))
3036

3137
for row in range(number_of_rows):
3238
for col in range(number_of_cols):
@@ -35,3 +41,25 @@ def dfs(r: int, c: int):
3541
dfs(row, col)
3642

3743
return islands
44+
45+
46+
def num_islands_union_find(grid: List[List[str]]) -> int:
47+
if not grid:
48+
return 0
49+
50+
cols = len(grid[0])
51+
rows = len(grid)
52+
uf = UnionFind(grid)
53+
54+
for r in range(rows):
55+
for c in range(cols):
56+
if grid[r][c] == "1":
57+
grid[r][c] = "0"
58+
59+
if r + 1 < rows and grid[r + 1][c] == "1":
60+
uf.union(r * cols + c, (r + 1) * cols + c)
61+
if c + 1 < cols and grid[r][c + 1] == "1":
62+
uf.union(r * cols + c, r * cols + c + 1)
63+
64+
count = uf.get_count()
65+
return count

0 commit comments

Comments
 (0)