269 lines
11 KiB
YAML
269 lines
11 KiB
YAML
title: Max Area of Island
|
||
slug: max-area-of-island
|
||
difficulty: medium
|
||
leetcode_id: 695
|
||
leetcode_url: https://leetcode.com/problems/max-area-of-island/
|
||
categories:
|
||
- graphs
|
||
- arrays
|
||
patterns:
|
||
- dfs
|
||
- bfs
|
||
- matrix-traversal
|
||
|
||
description: |
|
||
You are given an `m × n` binary matrix `grid`. An island is a group of `1`s (representing land) connected **4-directionally** (horizontal or vertical). You may assume all four edges of the grid are surrounded by water.
|
||
|
||
The **area** of an island is the number of cells with a value `1` in the island.
|
||
|
||
Return *the maximum **area** of an island in* `grid`. If there is no island, return `0`.
|
||
|
||
constraints: |
|
||
- `m == grid.length`
|
||
- `n == grid[i].length`
|
||
- `1 <= m, n <= 50`
|
||
- `grid[i][j]` is either `0` or `1`
|
||
|
||
examples:
|
||
- 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],
|
||
[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],
|
||
[0,0,0,0,0,0,0,1,1,0,0,0,0]
|
||
]
|
||
output: "6"
|
||
explanation: "The answer is not 11, because islands must be connected 4-directionally (not diagonally). The largest island has 6 cells."
|
||
- input: "grid = [[0,0,0,0,0,0,0,0]]"
|
||
output: "0"
|
||
explanation: "There is no land, so the maximum area is 0."
|
||
|
||
explanation:
|
||
intuition: |
|
||
This problem builds directly on "Number of Islands". Instead of counting how many islands exist, we want to find the **largest** one.
|
||
|
||
Imagine you're a cartographer mapping islands from above. When you spot a piece of land, you explore the entire island by walking to all connected land cells (up, down, left, right — no diagonals). As you explore, you count each cell you visit. After exploring the whole island, you record its area.
|
||
|
||
Think of it like this: each time you discover a new island, you measure its size by counting cells during your exploration. You keep track of the largest island you've found so far. When you're done scanning the entire map, the largest recorded measurement is your answer.
|
||
|
||
The key insight is that **DFS/BFS naturally counts area** — every cell we visit during an exploration is part of that island, so we just need to count how many cells we visit per island.
|
||
|
||
approach: |
|
||
We solve this using **DFS with Area Counting**:
|
||
|
||
**Step 1: Set up tracking variables**
|
||
|
||
- `max_area`: Set to `0` to track the largest island found
|
||
- `rows`, `cols`: Store grid dimensions for bounds checking
|
||
|
||
|
||
|
||
**Step 2: Iterate through every cell**
|
||
|
||
- Scan the grid row by row, column by column
|
||
- When we find a `1` (unvisited land), we've found a new island to explore
|
||
|
||
|
||
|
||
**Step 3: DFS exploration with counting**
|
||
|
||
- When visiting a land cell, mark it as `0` (visited) to prevent revisiting
|
||
- Count this cell as `1` area
|
||
- Recursively explore all four directions (up, down, left, right)
|
||
- Return the sum: `1 + area_from_up + area_from_down + area_from_left + area_from_right`
|
||
- Base case: return `0` if out of bounds or cell is water
|
||
|
||
|
||
|
||
**Step 4: Track the maximum**
|
||
|
||
- After each DFS completes, compare the island's area to `max_area`
|
||
- Update `max_area` if this island is larger
|
||
|
||
|
||
|
||
**Step 5: Return the result**
|
||
|
||
- After processing all cells, return `max_area`
|
||
|
||
|
||
|
||
This works because DFS visits every cell of an island exactly once, and by returning `1 + sum_of_neighbors`, we recursively accumulate the total area.
|
||
|
||
common_pitfalls:
|
||
- title: Forgetting to Return Area from DFS
|
||
description: |
|
||
A common mistake is to use a global or external variable to track area, but forget to properly accumulate it from recursive calls.
|
||
|
||
The cleanest approach is to have DFS return the area it explored:
|
||
- Base case returns `0` (water or out of bounds contributes no area)
|
||
- Recursive case returns `1 + sum of areas from all four directions`
|
||
|
||
This naturally accumulates the total area through the call stack.
|
||
wrong_approach: "Using external counter without proper accumulation"
|
||
correct_approach: "Return area from DFS: return 1 + dfs(up) + dfs(down) + dfs(left) + dfs(right)"
|
||
|
||
- title: Not Marking Cells Before Recursion
|
||
description: |
|
||
You must mark a cell as visited (set to `0`) **before** making recursive calls. Otherwise, when exploring neighbors, they might try to revisit the current cell, causing infinite recursion.
|
||
|
||
For example, if cell A explores cell B, and we haven't marked A as visited yet, then B will try to explore A, which explores B again — infinite loop!
|
||
wrong_approach: "Marking cell after recursive calls"
|
||
correct_approach: "grid[r][c] = 0 immediately upon entering the cell"
|
||
|
||
- title: Including Diagonal Connections
|
||
description: |
|
||
The problem specifies **4-directional** connectivity. Cells connected diagonally are NOT part of the same island.
|
||
|
||
Only explore the 4 orthogonal directions: up, down, left, right. This is a common source of wrong answers.
|
||
wrong_approach: "Exploring 8 directions including diagonals"
|
||
correct_approach: "Explore only (r±1, c) and (r, c±1)"
|
||
|
||
- title: Returning Wrong Value for Empty Grid
|
||
description: |
|
||
If the grid contains no land at all, the answer should be `0`. Initialising `max_area = 0` handles this automatically — if no island is ever found, `0` is returned.
|
||
|
||
Don't initialise `max_area` to `-1` or forget to handle the all-water case.
|
||
|
||
key_takeaways:
|
||
- "**DFS returns values**: Making DFS return the area it explores leads to clean, recursive accumulation"
|
||
- "**Build on simpler problems**: Max Area of Island is Number of Islands with one addition — counting during traversal"
|
||
- "**In-place marking**: Modifying the grid to track visited cells avoids extra space for a visited set"
|
||
- "**Pattern recognition**: Many grid problems use DFS/BFS to explore connected components; the only difference is what you track during exploration"
|
||
|
||
time_complexity: "O(m × n). Each cell is visited at most once by the main loop and at most once by DFS."
|
||
space_complexity: "O(m × n). In the worst case (all land in a snake pattern), the DFS recursion stack can hold all cells."
|
||
|
||
solutions:
|
||
- approach_name: DFS with Return Value
|
||
is_optimal: true
|
||
code: |
|
||
def max_area_of_island(grid: list[list[int]]) -> int:
|
||
if not grid:
|
||
return 0
|
||
|
||
rows, cols = len(grid), len(grid[0])
|
||
max_area = 0
|
||
|
||
def dfs(r: int, c: int) -> int:
|
||
# Base case: out of bounds or water
|
||
if r < 0 or r >= rows or c < 0 or c >= cols:
|
||
return 0
|
||
if grid[r][c] != 1:
|
||
return 0
|
||
|
||
# Mark as visited by "sinking" the land
|
||
grid[r][c] = 0
|
||
|
||
# Count this cell (1) plus all connected cells
|
||
return (1 +
|
||
dfs(r + 1, c) + # down
|
||
dfs(r - 1, c) + # up
|
||
dfs(r, c + 1) + # right
|
||
dfs(r, c - 1)) # left
|
||
|
||
# Scan every cell in the grid
|
||
for r in range(rows):
|
||
for c in range(cols):
|
||
if grid[r][c] == 1:
|
||
# Found new island — measure its area
|
||
area = dfs(r, c)
|
||
max_area = max(max_area, area)
|
||
|
||
return max_area
|
||
explanation: |
|
||
**Time Complexity:** O(m × n) — Each cell visited at most twice (once by loop, once by DFS).
|
||
|
||
**Space Complexity:** O(m × n) — Recursion stack in worst case (grid is all land in a snake pattern).
|
||
|
||
The DFS function returns the area it explores. When we find unvisited land, we call DFS which returns the total area of that island. We track the maximum area seen across all islands.
|
||
|
||
- approach_name: BFS with Queue
|
||
is_optimal: true
|
||
code: |
|
||
from collections import deque
|
||
|
||
def max_area_of_island(grid: list[list[int]]) -> int:
|
||
if not grid:
|
||
return 0
|
||
|
||
rows, cols = len(grid), len(grid[0])
|
||
max_area = 0
|
||
|
||
def bfs(start_r: int, start_c: int) -> int:
|
||
queue = deque([(start_r, start_c)])
|
||
grid[start_r][start_c] = 0 # Mark starting cell
|
||
area = 0
|
||
|
||
while queue:
|
||
r, c = queue.popleft()
|
||
area += 1 # Count this cell
|
||
|
||
# Explore all four directions
|
||
for dr, dc in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
||
nr, nc = r + dr, c + dc
|
||
|
||
# Add unvisited land to queue
|
||
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
|
||
grid[nr][nc] = 0 # Mark before adding
|
||
queue.append((nr, nc))
|
||
|
||
return area
|
||
|
||
for r in range(rows):
|
||
for c in range(cols):
|
||
if grid[r][c] == 1:
|
||
area = bfs(r, c)
|
||
max_area = max(max_area, area)
|
||
|
||
return max_area
|
||
explanation: |
|
||
**Time Complexity:** O(m × n) — Same as DFS.
|
||
|
||
**Space Complexity:** O(min(m, n)) — Queue holds at most one "frontier" layer.
|
||
|
||
BFS explores level by level. We count area by incrementing a counter each time we process a cell from the queue. Mark cells when adding to queue (not when processing) to avoid duplicates.
|
||
|
||
- approach_name: Iterative DFS with Stack
|
||
is_optimal: true
|
||
code: |
|
||
def max_area_of_island(grid: list[list[int]]) -> int:
|
||
if not grid:
|
||
return 0
|
||
|
||
rows, cols = len(grid), len(grid[0])
|
||
max_area = 0
|
||
|
||
for r in range(rows):
|
||
for c in range(cols):
|
||
if grid[r][c] == 1:
|
||
# Use stack for iterative DFS
|
||
stack = [(r, c)]
|
||
grid[r][c] = 0
|
||
area = 0
|
||
|
||
while stack:
|
||
cr, cc = stack.pop()
|
||
area += 1
|
||
|
||
# Explore all four directions
|
||
for dr, dc in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
||
nr, nc = cr + dr, cc + dc
|
||
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
|
||
grid[nr][nc] = 0
|
||
stack.append((nr, nc))
|
||
|
||
max_area = max(max_area, area)
|
||
|
||
return max_area
|
||
explanation: |
|
||
**Time Complexity:** O(m × n) — Same as recursive DFS.
|
||
|
||
**Space Complexity:** O(m × n) — Stack can hold all cells in worst case.
|
||
|
||
This avoids recursion by using an explicit stack. Useful in languages with limited recursion depth or when you prefer iterative solutions. The logic mirrors recursive DFS but manages the stack manually.
|