Skip to content

Commit 842dcc3

Browse files
committed
feat(algorithms, graphs): iterative solution to max area of island
1 parent 4ee639d commit 842dcc3

4 files changed

Lines changed: 171 additions & 43 deletions

File tree

algorithms/graphs/maxareaofisland/README.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,70 @@ The area of an island is the number of cells with a value 1 in the island.
77

88
Return the maximum area of an island in grid. If there is no island, return 0.
99

10+
## Examples
11+
1012
Example 1:
1113

1214
![max_area_island](maxarea-grid.jpg)
1315

16+
```text
1417
Input: grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0]
1518
,[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0]
1619
,[0,0,0,0,0,0,0,1,1,0,0,0,0]]
1720
Output: 6
1821
Explanation: The answer is not 11, because the island must be connected 4-directionally.
22+
```
1923

2024
Example 2:
21-
25+
```text
2226
Input: grid = [[0,0,0,0,0,0,0,0]]
2327
Output: 0
28+
```
29+
30+
## Solution
31+
32+
1. [Depth-First Search (Recursive)](#depth-first-search-recursive)
33+
2. [Depth-First Search (Iterative)](#depth-first-search-iterative)
34+
35+
### Depth-First Search (Recursive)
36+
37+
We want to know the area of each connected shape in the grid, then take the maximum of these.
38+
39+
If we are on a land square and explore every square connected to it 4-directionally (and recursively squares connected
40+
to those squares, and so on), then the total number of squares explored will be the area of that connected shape.
41+
42+
To ensure we don't count squares in a shape more than once, let's use `visited` to keep track of squares we haven't
43+
visited before. It will also prevent us from counting the same shape more than once.
44+
45+
#### Complexity Analysis
46+
47+
##### Time Complexity
48+
49+
`O(R*C)`, where R is the number of rows in the given grid, and C is the number of columns. We visit every square once.
50+
51+
##### Space Complexity
52+
53+
`O(R*C)`, the space used by seen to keep track of visited squares and the space used by the call stack during our
54+
recursion.
55+
56+
### Depth-First Search (Iterative)
57+
58+
We can try the same approach using a stack-based, (or "iterative") depth-first search.
59+
60+
Here, `visited` will represent squares that have either been visited or are added to our list of squares to visit (`stack`).
61+
For every starting land square that hasn't been visited, we will explore 4-directionally around it, adding land squares
62+
that haven't been added to `visited` to our stack.
63+
64+
On the side, we'll keep a count `shape` of the total number of squares seen during the exploration of this shape. We'll
65+
want the running max of these counts.
66+
67+
#### Complexity Analysis
68+
69+
##### Time Complexity
70+
71+
`O(R*C)`, where R is the number of rows in the given grid, and C is the number of columns. We visit every square once.
72+
73+
##### Space Complexity
74+
75+
`O(R*C)`, the space used by seen to keep track of visited squares and the space used by stack
76+
Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List
1+
from typing import List, Tuple, Set
22

33

44
def max_area_of_island(grid: List[List[int]]) -> int:
@@ -7,11 +7,15 @@ def max_area_of_island(grid: List[List[int]]) -> int:
77
connected 4-directionally (horizontal or vertical.) You may assume all 4
88
edges of the grid are surrounded by water.
99
this returns the maximum area of an island in the grid
10+
Args:
11+
grid(list): 2D array grid of 0s and 1s
12+
Returns:
13+
int: max area of island
1014
"""
1115
num_rows, num_cols = len(grid), len(grid[0])
1216
max_area = 0
1317

14-
visited = set()
18+
visited: Set[Tuple[int, int]] = set()
1519

1620
def area(row: int, col: int) -> int:
1721
if not (
@@ -38,21 +42,41 @@ def area(row: int, col: int) -> int:
3842
return max_area
3943

4044

41-
def dfs(grid: List[List[int]], row: int, col: int) -> int:
42-
visited = set()
43-
44-
if (
45-
not (0 <= row < len(grid) and 0 <= col < len(grid[0]))
46-
and (row, col) not in visited
47-
and grid[row][col]
48-
):
49-
return 0
50-
51-
visited.add((row, col))
52-
return (
53-
1
54-
+ dfs(grid, row + 1, col)
55-
+ dfs(grid, row - 1, col)
56-
+ dfs(grid, row, col + 1)
57-
+ dfs(grid, row, col - 1)
58-
)
45+
def max_area_of_island_iterative(grid: List[List[int]]) -> int:
46+
"""
47+
Given a non-empty 2D array grid of 0s and 1s, an island is a group of 1s
48+
connected 4-directionally (horizontal or vertical.) You may assume all 4
49+
edges of the grid are surrounded by water.
50+
this returns the maximum area of an island in the grid
51+
Args:
52+
grid(list): 2D array grid of 0s and 1s
53+
Returns:
54+
int: max area of island
55+
"""
56+
num_rows, num_cols = len(grid), len(grid[0])
57+
max_area = 0
58+
59+
visited: Set[Tuple[int, int]] = set()
60+
61+
for row_idx, row in enumerate(grid):
62+
for col_idx, val in enumerate(row):
63+
if val and (row_idx, col_idx) not in visited:
64+
shape = 0
65+
stack = [(row_idx, col_idx)]
66+
visited.add((row_idx, col_idx))
67+
68+
while stack:
69+
r, c = stack.pop()
70+
shape += 1
71+
for nr, nc in ((r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)):
72+
if (
73+
0 <= nr < num_rows
74+
and 0 <= nc < num_cols
75+
and grid[nr][nc]
76+
and (nr, nc) not in visited
77+
):
78+
stack.append((nr, nc))
79+
visited.add((nr, nc))
80+
max_area = max(max_area, shape)
81+
82+
return max_area

algorithms/graphs/maxareaofisland/test_max_area_of_island.py

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
import unittest
2+
from typing import List
3+
from parameterized import parameterized
4+
from utils.test_utils import custom_test_name_func
5+
from algorithms.graphs.maxareaofisland import (
6+
max_area_of_island,
7+
max_area_of_island_iterative,
8+
)
29

3-
from . import max_area_of_island
4-
5-
6-
class TestMaxAreaOfIsland(unittest.TestCase):
7-
def test_empty_grid(self):
8-
"""should return 0 for empty grid"""
9-
grid = [[]]
10-
expected = 0
11-
self.assertEqual(max_area_of_island(grid), expected)
12-
13-
def test_grid_with_no_islands(self):
14-
"""should return 0 for grid with no islands"""
15-
grid = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
16-
expected = 0
17-
actual = max_area_of_island(grid)
18-
self.assertEqual(expected, actual)
19-
20-
def test_should_return_max_area_of_island(self):
21-
grid = [
10+
MAX_AREA_OF_ISLAND_TEST_CASES = [
11+
([[]], 0),
12+
([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], 0),
13+
(
14+
[
2215
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
2316
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
2417
[0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
@@ -27,10 +20,60 @@ def test_should_return_max_area_of_island(self):
2720
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
2821
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
2922
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
30-
]
31-
expected = 6
23+
],
24+
6,
25+
),
26+
([[1, 1, 0], [0, 1, 0], [0, 0, 1]], 3),
27+
([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], 0),
28+
([[1, 1, 1, 1, 1], [1, 1, 1, 1, 1]], 10),
29+
([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], 1),
30+
(
31+
[
32+
[0, 1, 1, 0, 0, 0],
33+
[0, 1, 1, 1, 0, 0],
34+
[0, 0, 0, 1, 1, 1],
35+
[1, 0, 0, 0, 0, 1],
36+
],
37+
9,
38+
),
39+
]
40+
41+
42+
class TestMaxAreaOfIsland(unittest.TestCase):
43+
@parameterized.expand(
44+
MAX_AREA_OF_ISLAND_TEST_CASES, name_func=custom_test_name_func
45+
)
46+
def test_max_area_of_island_recursive(self, grid: List[List[int]], expected: int):
47+
"""should return 0 for empty grid"""
3248
actual = max_area_of_island(grid)
33-
self.assertEqual(expected, actual)
49+
self.assertEqual(actual, expected)
50+
51+
@parameterized.expand(MAX_AREA_OF_ISLAND_TEST_CASES)
52+
def test_max_area_of_island_iterative(self, grid: List[List[int]], expected: int):
53+
actual = max_area_of_island_iterative(grid)
54+
self.assertEqual(actual, expected)
55+
56+
# def test_grid_with_no_islands(self):
57+
# """should return 0 for grid with no islands"""
58+
# grid = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
59+
# expected = 0
60+
# actual = max_area_of_island(grid)
61+
# self.assertEqual(expected, actual)
62+
#
63+
# def test_should_return_max_area_of_island(self):
64+
# grid = [
65+
# [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
66+
# [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
67+
# [0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
68+
# [0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0],
69+
# [0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0],
70+
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
71+
# [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
72+
# [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
73+
# ]
74+
# expected = 6
75+
# actual = max_area_of_island(grid)
76+
# self.assertEqual(expected, actual)
3477

3578

3679
if __name__ == "__main__":

utils/test_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from parameterized import parameterized
2+
3+
4+
def custom_test_name_func(testcase_func, param_num, param):
5+
"""
6+
A custom name function that displays the name of the function under test along with the arguments and parameters
7+
"""
8+
return f"{testcase_func.__name__}, {parameterized.to_safe_name("_".join(str(x) for x in param.args))}"

0 commit comments

Comments
 (0)