questions S-W
This commit is contained in:
289
backend/data/questions/surrounded-regions.yaml
Normal file
289
backend/data/questions/surrounded-regions.yaml
Normal file
@@ -0,0 +1,289 @@
|
||||
title: Surrounded Regions
|
||||
slug: surrounded-regions
|
||||
difficulty: medium
|
||||
leetcode_id: 130
|
||||
leetcode_url: https://leetcode.com/problems/surrounded-regions/
|
||||
categories:
|
||||
- graphs
|
||||
- arrays
|
||||
patterns:
|
||||
- dfs
|
||||
- bfs
|
||||
- matrix-traversal
|
||||
|
||||
description: |
|
||||
You are given an `m x n` matrix `board` containing letters `'X'` and `'O'`. **Capture** all regions that are **surrounded**:
|
||||
|
||||
- **Connect**: A cell is connected to adjacent cells horizontally or vertically.
|
||||
- **Region**: To form a region, connect every `'O'` cell.
|
||||
- **Surround**: A region is surrounded with `'X'` cells if you can connect the region with `'X'` cells and **none** of the region cells are on the edge of the board.
|
||||
|
||||
To capture a surrounded region, replace all `'O'`s with `'X'`s **in-place** within the original board. You do not need to return anything.
|
||||
|
||||
constraints: |
|
||||
- `m == board.length`
|
||||
- `n == board[i].length`
|
||||
- `1 <= m, n <= 200`
|
||||
- `board[i][j]` is `'X'` or `'O'`
|
||||
|
||||
examples:
|
||||
- input: 'board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]'
|
||||
output: '[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]'
|
||||
explanation: "The O's in the center form a surrounded region and are captured. The O at the bottom-left edge is not surrounded (it touches the boundary), so it remains unchanged."
|
||||
- input: 'board = [["X"]]'
|
||||
output: '[["X"]]'
|
||||
explanation: "A single X cell has no O's to capture."
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
Imagine the board as an island map where `'O'` cells are land and `'X'` cells are water. A region of `'O'`s is "surrounded" if it has no connection to the boundary of the map — it's completely enclosed by water.
|
||||
|
||||
The **key insight** is to think about this problem *backwards*: instead of finding regions that ARE surrounded, find regions that are NOT surrounded (those connected to the boundary), and protect them. Everything else gets captured.
|
||||
|
||||
Think of it like this: any `'O'` on the edge of the board is automatically safe — it can never be fully surrounded. Furthermore, any `'O'` connected to an edge `'O'` is also safe, because the whole connected component touches the boundary.
|
||||
|
||||
So the strategy becomes:
|
||||
1. Start from all `'O'`s on the boundary
|
||||
2. Mark all `'O'`s connected to them as "safe"
|
||||
3. Everything NOT marked safe is surrounded and should be captured
|
||||
|
||||
This "reverse thinking" transforms a complex region-finding problem into a simpler boundary-flood problem.
|
||||
|
||||
approach: |
|
||||
We solve this using a **Boundary DFS/BFS Approach**:
|
||||
|
||||
**Step 1: Identify boundary O's**
|
||||
|
||||
- Iterate through all cells on the four edges of the board (first row, last row, first column, last column)
|
||||
- When you find an `'O'` on the boundary, it cannot be captured
|
||||
|
||||
|
||||
|
||||
**Step 2: Mark safe regions**
|
||||
|
||||
- From each boundary `'O'`, perform DFS (or BFS) to visit all connected `'O'` cells
|
||||
- Mark these cells with a temporary marker (e.g., `'T'` for "temporary" or "safe")
|
||||
- This marks the entire connected component as uncapturable
|
||||
|
||||
|
||||
|
||||
**Step 3: Capture and restore**
|
||||
|
||||
- Iterate through the entire board
|
||||
- Any `'O'` remaining is surrounded — convert it to `'X'`
|
||||
- Any `'T'` (temporary marker) is safe — restore it to `'O'`
|
||||
|
||||
|
||||
|
||||
This three-phase approach ensures we correctly identify which regions touch the boundary and which are truly surrounded.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Trying to Find Surrounded Regions Directly
|
||||
description: |
|
||||
A natural first instinct is to iterate through the board, find each `'O'` region, and check if it's surrounded. This is complex because you need to:
|
||||
- Track all cells in a region
|
||||
- Check if ANY cell touches the boundary
|
||||
- Only then decide to capture or not
|
||||
|
||||
The boundary-first approach is simpler: mark safe cells first, then capture everything else in one pass.
|
||||
wrong_approach: "Find each O region and check if surrounded"
|
||||
correct_approach: "Mark boundary-connected O's first, then capture the rest"
|
||||
|
||||
- title: Forgetting to Check All Four Boundaries
|
||||
description: |
|
||||
The board has four edges: top row, bottom row, left column, and right column. Missing any edge means some safe `'O'`s won't be marked, leading to incorrect captures.
|
||||
|
||||
Make sure to iterate through:
|
||||
- Row 0 and row `m-1` (top and bottom)
|
||||
- Column 0 and column `n-1` (left and right)
|
||||
wrong_approach: "Only checking top and left edges"
|
||||
correct_approach: "Check all four edges of the board"
|
||||
|
||||
- title: Stack Overflow with Deep Recursion
|
||||
description: |
|
||||
With a 200x200 board, a region could contain up to 40,000 cells. Naive recursive DFS might cause stack overflow on such large connected components.
|
||||
|
||||
Solutions:
|
||||
- Use iterative DFS with an explicit stack
|
||||
- Use BFS with a queue
|
||||
- Increase recursion limit (not recommended)
|
||||
wrong_approach: "Deep recursive DFS on large boards"
|
||||
correct_approach: "Iterative DFS or BFS for large inputs"
|
||||
|
||||
- title: Modifying While Searching
|
||||
description: |
|
||||
If you try to capture `'O'`s to `'X'`s while still searching, you might accidentally disconnect parts of a safe region before fully exploring it.
|
||||
|
||||
The temporary marker (`'T'`) prevents this — it distinguishes "visited safe" cells from both `'O'` (unvisited) and `'X'` (wall/captured).
|
||||
|
||||
key_takeaways:
|
||||
- "**Reverse thinking**: Sometimes it's easier to find what you DON'T want and protect it, rather than directly finding what you want"
|
||||
- "**Boundary-connected components**: Problems involving 'surrounded' often reduce to finding what's connected to the boundary"
|
||||
- "**Temporary markers**: Using a third state (`'T'`) allows clean separation of visited-safe, unvisited, and captured cells"
|
||||
- "**Pattern recognition**: This is similar to Number of Islands, but with the twist of boundary connectivity — recognise the DFS/BFS on grids pattern"
|
||||
|
||||
time_complexity: "O(m * n). We visit each cell at most twice: once during the boundary DFS/BFS marking phase, and once during the final capture/restore pass."
|
||||
space_complexity: "O(m * n) in the worst case for the recursion stack or BFS queue, if almost all cells are `'O'` and connected. The modification is done in-place, so no additional board copy is needed."
|
||||
|
||||
solutions:
|
||||
- approach_name: Boundary DFS
|
||||
is_optimal: true
|
||||
code: |
|
||||
def solve(board: list[list[str]]) -> None:
|
||||
if not board or not board[0]:
|
||||
return
|
||||
|
||||
m, n = len(board), len(board[0])
|
||||
|
||||
def dfs(r: int, c: int) -> None:
|
||||
# Out of bounds or not an O — stop
|
||||
if r < 0 or r >= m or c < 0 or c >= n or board[r][c] != 'O':
|
||||
return
|
||||
|
||||
# Mark as safe (temporary marker)
|
||||
board[r][c] = 'T'
|
||||
|
||||
# Explore all four directions
|
||||
dfs(r + 1, c) # down
|
||||
dfs(r - 1, c) # up
|
||||
dfs(r, c + 1) # right
|
||||
dfs(r, c - 1) # left
|
||||
|
||||
# Step 1 & 2: Mark all O's connected to boundary
|
||||
for i in range(m):
|
||||
dfs(i, 0) # left edge
|
||||
dfs(i, n - 1) # right edge
|
||||
for j in range(n):
|
||||
dfs(0, j) # top edge
|
||||
dfs(m - 1, j) # bottom edge
|
||||
|
||||
# Step 3: Capture surrounded O's, restore safe T's
|
||||
for i in range(m):
|
||||
for j in range(n):
|
||||
if board[i][j] == 'O':
|
||||
board[i][j] = 'X' # Capture surrounded
|
||||
elif board[i][j] == 'T':
|
||||
board[i][j] = 'O' # Restore safe
|
||||
explanation: |
|
||||
**Time Complexity:** O(m * n) — Each cell is visited at most twice.
|
||||
|
||||
**Space Complexity:** O(m * n) — Recursion stack in worst case.
|
||||
|
||||
We start DFS from every boundary `'O'`, marking connected cells as `'T'` (safe). Then we sweep through the board: remaining `'O'`s are captured, `'T'`s are restored. This cleanly separates boundary-connected regions from surrounded ones.
|
||||
|
||||
- approach_name: Boundary BFS
|
||||
is_optimal: true
|
||||
code: |
|
||||
from collections import deque
|
||||
|
||||
def solve(board: list[list[str]]) -> None:
|
||||
if not board or not board[0]:
|
||||
return
|
||||
|
||||
m, n = len(board), len(board[0])
|
||||
queue = deque()
|
||||
|
||||
# Step 1: Collect all boundary O's
|
||||
for i in range(m):
|
||||
if board[i][0] == 'O':
|
||||
queue.append((i, 0))
|
||||
if board[i][n - 1] == 'O':
|
||||
queue.append((i, n - 1))
|
||||
for j in range(n):
|
||||
if board[0][j] == 'O':
|
||||
queue.append((0, j))
|
||||
if board[m - 1][j] == 'O':
|
||||
queue.append((m - 1, j))
|
||||
|
||||
# Step 2: BFS to mark all safe O's
|
||||
while queue:
|
||||
r, c = queue.popleft()
|
||||
if r < 0 or r >= m or c < 0 or c >= n:
|
||||
continue
|
||||
if board[r][c] != 'O':
|
||||
continue
|
||||
|
||||
board[r][c] = 'T' # Mark as safe
|
||||
|
||||
# Add neighbors to explore
|
||||
queue.append((r + 1, c))
|
||||
queue.append((r - 1, c))
|
||||
queue.append((r, c + 1))
|
||||
queue.append((r, c - 1))
|
||||
|
||||
# Step 3: Capture and restore
|
||||
for i in range(m):
|
||||
for j in range(n):
|
||||
if board[i][j] == 'O':
|
||||
board[i][j] = 'X' # Capture
|
||||
elif board[i][j] == 'T':
|
||||
board[i][j] = 'O' # Restore
|
||||
explanation: |
|
||||
**Time Complexity:** O(m * n) — Each cell processed at most once.
|
||||
|
||||
**Space Complexity:** O(m * n) — Queue size in worst case.
|
||||
|
||||
BFS avoids recursion depth issues. We seed the queue with all boundary `'O'`s, then expand outward marking safe cells. The final pass captures and restores just like the DFS approach. BFS is often preferred for very large grids.
|
||||
|
||||
- approach_name: Union-Find
|
||||
is_optimal: false
|
||||
code: |
|
||||
def solve(board: list[list[str]]) -> None:
|
||||
if not board or not board[0]:
|
||||
return
|
||||
|
||||
m, n = len(board), len(board[0])
|
||||
|
||||
# Union-Find with path compression
|
||||
parent = list(range(m * n + 1))
|
||||
rank = [0] * (m * n + 1)
|
||||
dummy = m * n # Virtual node for boundary-connected cells
|
||||
|
||||
def find(x: int) -> int:
|
||||
if parent[x] != x:
|
||||
parent[x] = find(parent[x]) # Path compression
|
||||
return parent[x]
|
||||
|
||||
def union(x: int, y: int) -> None:
|
||||
px, py = find(x), find(y)
|
||||
if px == py:
|
||||
return
|
||||
# Union by rank
|
||||
if rank[px] < rank[py]:
|
||||
px, py = py, px
|
||||
parent[py] = px
|
||||
if rank[px] == rank[py]:
|
||||
rank[px] += 1
|
||||
|
||||
def index(r: int, c: int) -> int:
|
||||
return r * n + c
|
||||
|
||||
# Build unions
|
||||
for i in range(m):
|
||||
for j in range(n):
|
||||
if board[i][j] != 'O':
|
||||
continue
|
||||
|
||||
idx = index(i, j)
|
||||
|
||||
# Connect boundary O's to dummy node
|
||||
if i == 0 or i == m - 1 or j == 0 or j == n - 1:
|
||||
union(idx, dummy)
|
||||
|
||||
# Connect to adjacent O's
|
||||
if i > 0 and board[i - 1][j] == 'O':
|
||||
union(idx, index(i - 1, j))
|
||||
if j > 0 and board[i][j - 1] == 'O':
|
||||
union(idx, index(i, j - 1))
|
||||
|
||||
# Capture cells not connected to dummy
|
||||
for i in range(m):
|
||||
for j in range(n):
|
||||
if board[i][j] == 'O' and find(index(i, j)) != find(dummy):
|
||||
board[i][j] = 'X'
|
||||
explanation: |
|
||||
**Time Complexity:** O(m * n * α(m * n)) — Nearly linear due to path compression.
|
||||
|
||||
**Space Complexity:** O(m * n) — Parent and rank arrays.
|
||||
|
||||
Union-Find groups all `'O'`s into connected components. Boundary `'O'`s are connected to a virtual "dummy" node. After processing, any `'O'` not in the dummy's component is surrounded and captured. This approach is more complex but demonstrates the Union-Find pattern for connectivity problems.
|
||||
Reference in New Issue
Block a user