Files
codetutor/backend/data/patterns/matrix-traversal.yaml

302 lines
8.7 KiB
YAML

name: Matrix Traversal
slug: matrix-traversal
difficulty_level: 3
pattern_type: algorithm
display_order: 9
description: >
Navigate 2D grids systematically using DFS, BFS, or directional iteration.
Matrix traversal combines graph traversal concepts with coordinate-based
movement, treating each cell as a node connected to its neighbors.
when_to_use: |
- Finding connected regions (islands)
- Shortest path in a grid
- Flood fill / painting
- Spreading simulations (rotting oranges, fire spread)
- Word search in character grid
metaphor: |
Imagine being dropped into a maze viewed from above. You can move up, down,
left, or right, but walls block certain paths. DFS is like exploring one
corridor completely before backtracking. BFS is like sending scouts in all
directions simultaneously—whoever reaches the exit first found the shortest path.
Another analogy: painting a floor. Flood fill (DFS/BFS) starts at one point
and spreads to all connected unpainted tiles.
core_concept: |
A 2D grid is essentially a graph where each cell connects to its neighbors
(4-directional or 8-directional). The key adaptations from graph traversal:
1. **Nodes are coordinates**: Instead of node IDs, track `(row, col)` pairs
2. **Edges are directions**: Neighbors are found by adding direction vectors
3. **Bounds checking**: Validate coordinates before accessing the grid
4. **In-place visited marking**: Often modify the grid itself instead of using
a separate visited set (mark '1' → '0' to indicate visited)
**When to use DFS vs BFS:**
- **DFS**: Counting regions, checking if path exists, flood fill
- **BFS**: Shortest path, simultaneous spreading from multiple sources
visualization: |
**Example: Number of Islands**
```
Grid (1 = land, 0 = water):
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
DFS from (0,0): marks all connected 1s as visited
→ Found island 1
Continue scanning, DFS from (2,2):
→ Found island 2
Continue scanning, DFS from (3,3):
→ Found island 3
Answer: 3 islands
```
**Shortest path in grid (BFS):**
```
Grid (0 = passable, 1 = wall):
0 0 0
1 1 0
0 0 0
Start: (0,0) End: (2,2)
BFS levels:
Level 0: (0,0)
Level 1: (0,1)
Level 2: (0,2)
Level 3: (1,2)
Level 4: (2,2) ← Found! Distance = 4
```
**4-directional vs 8-directional:**
```
4-directional: 8-directional:
↑ ↖ ↑ ↗
← ○ → ← ○ →
↓ ↙ ↓ ↘
```
code_template: |
from collections import deque
# Direction vectors
DIRS_4 = [(0, 1), (0, -1), (1, 0), (-1, 0)] # right, left, down, up
DIRS_8 = [(0, 1), (0, -1), (1, 0), (-1, 0),
(1, 1), (1, -1), (-1, 1), (-1, -1)]
def is_valid(grid: list[list], row: int, col: int) -> bool:
"""Check if coordinates are within grid bounds."""
return 0 <= row < len(grid) and 0 <= col < len(grid[0])
def dfs_matrix(grid: list[list[int]], row: int, col: int) -> int:
"""DFS to explore connected region, returns size."""
if not is_valid(grid, row, col) or grid[row][col] == 0:
return 0
grid[row][col] = 0 # Mark as visited (modify in-place)
size = 1
for dr, dc in DIRS_4:
size += dfs_matrix(grid, row + dr, col + dc)
return size
def count_islands(grid: list[list[str]]) -> int:
"""Count connected regions of '1's."""
if not grid:
return 0
count = 0
rows, cols = len(grid), len(grid[0])
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
count += 1
dfs_sink_island(grid, r, c)
return count
def dfs_sink_island(grid: list[list[str]], row: int, col: int):
"""Sink an island by marking all connected '1's as '0'."""
if not is_valid(grid, row, col) or grid[row][col] == '0':
return
grid[row][col] = '0' # Sink this cell
for dr, dc in DIRS_4:
dfs_sink_island(grid, row + dr, col + dc)
def bfs_shortest_path(grid: list[list[int]],
start: tuple, end: tuple) -> int:
"""BFS to find shortest path in grid (0 = passable, 1 = wall)."""
if grid[start[0]][start[1]] == 1 or grid[end[0]][end[1]] == 1:
return -1
rows, cols = len(grid), len(grid[0])
queue = deque([(*start, 0)]) # (row, col, distance)
visited = {start}
while queue:
row, col, dist = queue.popleft()
if (row, col) == end:
return dist
for dr, dc in DIRS_4:
nr, nc = row + dr, col + dc
if (is_valid(grid, nr, nc)
and (nr, nc) not in visited
and grid[nr][nc] == 0):
visited.add((nr, nc))
queue.append((nr, nc, dist + 1))
return -1 # No path
def multi_source_bfs(grid: list[list[int]]) -> int:
"""BFS from multiple sources (e.g., rotting oranges)."""
rows, cols = len(grid), len(grid[0])
queue = deque()
fresh = 0
# Find all sources and count targets
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2: # Rotten orange
queue.append((r, c, 0))
elif grid[r][c] == 1: # Fresh orange
fresh += 1
max_time = 0
while queue:
row, col, time = queue.popleft()
max_time = max(max_time, time)
for dr, dc in DIRS_4:
nr, nc = row + dr, col + dc
if is_valid(grid, nr, nc) and grid[nr][nc] == 1:
grid[nr][nc] = 2 # Mark as rotten
fresh -= 1
queue.append((nr, nc, time + 1))
return max_time if fresh == 0 else -1
recognition_signals:
- "grid"
- "matrix"
- "2D array"
- "number of islands"
- "shortest path in grid"
- "flood fill"
- "surrounded regions"
- "rotting oranges"
- "word search"
- "robot path"
- "maze"
common_mistakes:
- title: Modifying grid while using it for bounds checking
description: |
If you use grid values for validity checks (like `grid[r][c] == '1'`)
and also modify them in-place, you might check a cell you already modified.
fix: |
Modify the cell immediately upon visiting, before exploring neighbors:
```python
grid[row][col] = '0' # Mark first
for dr, dc in DIRS_4:
dfs(grid, row + dr, col + dc) # Then explore
```
- title: Forgetting to check all starting points
description: |
For problems like "number of islands," only starting DFS from (0,0) misses
islands not connected to the top-left.
fix: |
Iterate through every cell and start a new traversal from each unvisited cell:
```python
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
count += 1
dfs(grid, r, c)
```
- title: Index out of bounds
description: |
Accessing `grid[row][col]` without checking bounds first causes runtime
errors when exploring neighbors.
fix: |
Always check bounds before accessing:
```python
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == target:
# Safe to access
```
- title: Using wrong direction set
description: |
Using 4-directional movement when problem requires 8-directional (diagonal)
gives wrong results, or vice versa.
fix: |
Read the problem carefully. "Adjacent" usually means 4-directional.
"Neighboring" or explicit diagonal mention means 8-directional.
variations:
- name: Island counting
description: |
Count connected regions of the same value. DFS from each unvisited cell,
marking the entire region as visited.
example: "Number of Islands, Max Area of Island"
- name: Shortest path in grid
description: |
BFS from start to end, counting levels. Works only if all moves have
equal cost (unweighted).
example: "Shortest Path in Binary Matrix, 01 Matrix"
- name: Multi-source BFS
description: |
Start BFS from multiple cells simultaneously. Useful for spreading
problems where multiple sources expand in parallel.
example: "Rotting Oranges, Walls and Gates"
- name: Word search
description: |
DFS with backtracking to find if a word exists in the grid by following
adjacent cells. Need to track the current path to avoid reusing cells.
example: "Word Search, Word Search II"
- name: Spiral traversal
description: |
Not graph-based, but iterate through matrix in spiral order using
boundary tracking (top, bottom, left, right).
example: "Spiral Matrix, Spiral Matrix II"
related_patterns:
- dfs
- bfs
- backtracking
prerequisite_patterns:
- dfs
- bfs