Surrounded Regions
Problem
Given an m x n matrix board containing 'X' and 'O', capture all regions surrounded by 'X' by flipping all 'O's that are not connected to the border to 'X'.
- m == board.length
- n == board[i].length
- 1 ≤ m, n ≤ 200
- board[i][j] is 'X' or 'O'
Example
board = [['X','X','X','X'],['X','O','O','X'],['X','X','O','X'],['X','O','X','X']][['X','X','X','X'],['X','X','X','X'],['X','X','X','X'],['X','O','X','X']]The 'O's in the middle are surrounded by 'X' and flipped to 'X'. The 'O' at the bottom left corner is connected to the border, so it remains 'O'. The algorithm marks all 'O's connected to the border with a temporary marker 'T' using DFS, then flips all remaining 'O's to 'X', and finally restores 'T' back to 'O'.
Approach
Straightforward Solution
A naive approach would check each 'O' and perform a flood fill to determine if it is surrounded, which can be expensive and redundant, leading to O(m*n*(m+n)) time complexity.
Core Observation
Any 'O' connected to the border cannot be captured because it is not fully surrounded by 'X'. Therefore, the problem reduces to identifying and preserving all 'O's connected to the border.
Path to Optimal
The key insight is to invert the problem: instead of searching for surrounded regions, start from the border and mark all 'O's connected to the border as safe. This can be done efficiently with DFS or BFS. After marking, flip all unmarked 'O's to 'X' and restore the marked ones back to 'O'. This approach ensures each cell is visited at most once, achieving O(m*n) time.
Optimal Approach
Use DFS starting from all border cells that contain 'O'. Mark these and all connected 'O's with a temporary marker 'T'. Then iterate over the board to flip all remaining 'O's to 'X' (captured regions) and convert 'T's back to 'O' (safe regions). This approach guarantees correctness and efficiency.
Time
O(m * n)
Each cell is visited at most once during the DFS marking and once during the final iteration to flip cells, resulting in linear time relative to the board size.
Space
O(m * n)
In the worst case, the recursion stack for DFS can grow up to O(m * n) if the entire board is filled with 'O's connected to the border. The in-place marking avoids extra data structures, but recursion stack space is required.
Pattern Spotlight
DFS (Boundary-Connected Region Marking)
When a problem requires identifying regions connected to boundaries, invert the problem by starting DFS from the boundary to mark safe regions, then process the rest accordingly.
Solution
| 1 | class Solution:
|
| 2 | def solve(self, board: list[list[str]]) -> None:
|
| 3 | ROWS, COLS = len(board), len(board[0])
|
| 4 |
|
| 5 | def capture(r, c):
|
| 6 | if r < 0 or c < 0 or r == ROWS or c == COLS or board[r][c] != "O":
|
| 7 | return
|
| 8 |
|
| 9 | board[r][c] = "T"
|
| 10 | capture(r + 1, c)
|
| 11 | capture(r - 1, c)
|
| 12 | capture(r, c + 1)
|
| 13 | capture(r, c - 1)
|
| 14 |
|
| 15 | for r in range(ROWS):
|
| 16 | for c in range(COLS):
|
| 17 | if board[r][c] == "O" and (r in [0, ROWS - 1] or c in [0, COLS - 1]):
|
| 18 | capture(r, c)
|
| 19 |
|
| 20 | for r in range(ROWS):
|
| 21 | for c in range(COLS):
|
| 22 | if board[r][c] == "O":
|
| 23 | board[r][c] = "X"
|
| 24 |
|
| 25 | for r in range(ROWS):
|
| 26 | for c in range(COLS):
|
| 27 | if board[r][c] == "T":
|
| 28 | board[r][c] = "O" |
Step-by-Step Solution
Mark Border-Connected 'O's Using DFS to Protect Safe Regions
| 5 | def capture(r, c):
|
| 6 | if r < 0 or c < 0 or r == ROWS or c == COLS or board[r][c] != "O":
|
| 7 | return
|
| 9 | board[r][c] = "T"
|
| 10 | capture(r + 1, c)
|
| 11 | capture(r - 1, c)
|
| 12 | capture(r, c + 1)
|
| 13 | capture(r, c - 1)
|
| 15 | for r in range(ROWS):
|
| 16 | for c in range(COLS):
|
| 17 | if board[r][c] == "O" and (r in [0, ROWS - 1] or c in [0, COLS - 1]):
|
| 18 | capture(r, c)
|
Objective
To identify and mark all 'O's connected to the border as safe by performing DFS from border cells.
Key Insight
Starting DFS from border 'O's ensures that all connected 'O's are marked as safe in a single pass. Marking them with a temporary character prevents revisiting and distinguishes safe 'O's from those that should be captured. This approach avoids redundant checks and efficiently isolates the regions that must remain unchanged.
Interview Quick-Check
Core Logic
DFS explores all 'O's connected to the border, marking them to prevent flipping, effectively separating safe regions from capturable ones.
State & Boundaries
The DFS base case checks for out-of-bounds indices and cells that are not 'O', ensuring recursion terminates correctly.
Common Pitfalls & Bugs
Failing to mark visited 'O's leads to infinite recursion or repeated work; using a temporary marker like 'T' prevents this.
Complexity
Each cell is visited at most once during DFS, ensuring O(m*n) time complexity.
Flip Unmarked 'O's to 'X' to Capture Surrounded Regions
| 20 | for r in range(ROWS):
|
| 21 | for c in range(COLS):
|
| 22 | if board[r][c] == "O":
|
| 23 | board[r][c] = "X"
|
Objective
To convert all 'O's not connected to the border into 'X's, effectively capturing surrounded regions.
Key Insight
After marking safe 'O's, any remaining 'O' must be fully surrounded by 'X's and thus should be flipped. This step finalizes the capture by scanning the entire board and flipping unmarked 'O's to 'X's, ensuring correctness without revisiting the DFS logic.
Interview Quick-Check
Core Logic
Flipping unmarked 'O's to 'X's captures all surrounded regions without affecting safe regions.
State & Boundaries
Iterate over the entire board to ensure no 'O' is missed.
Common Pitfalls & Bugs
Forgetting to flip all unmarked 'O's leads to incorrect results where surrounded regions remain unflipped.
Restore Marked 'T's Back to 'O's to Finalize Safe Regions
| 25 | for r in range(ROWS):
|
| 26 | for c in range(COLS):
|
| 27 | if board[r][c] == "T":
|
| 28 | board[r][c] = "O" |
Objective
To revert the temporary marker 'T' back to 'O' to restore the original safe regions.
Key Insight
The temporary marker 'T' distinguishes safe 'O's during processing. Restoring them back to 'O' completes the transformation, ensuring the board reflects the correct captured and safe regions. This final pass cleans up the board state without additional DFS or complex logic.
Interview Quick-Check
Core Logic
Restoring 'T' to 'O' recovers the original safe regions after surrounded regions have been captured.
State & Boundaries
A full board scan ensures all marked cells are restored.
Common Pitfalls & Bugs
Neglecting this step leaves temporary markers in the final output, violating problem requirements.
Line Analysis
This solution has 4 Critical lines interviewers watch for.
board[r][c] = "T"
Mark the current 'O' cell as 'T' to indicate it is safe.
This marking is the critical step that prevents infinite recursion and correctly identifies safe regions, enabling the algorithm to separate safe from capturable 'O's.
if r < 0 or c < 0 or r == ROWS or c == COLS or board[r][c] != "O":
Check if the current cell is out of bounds or not an 'O'.
This base case prevents invalid access and stops recursion on cells that are either 'X' or already marked, ensuring correctness and termination.
capture(r, c)
Invoke DFS to mark all connected safe 'O's starting from this border cell.
This call triggers the marking of all safe 'O's connected to the border, ensuring they are preserved.
board[r][c] = "X"
Flip the captured 'O' to 'X'.
This line performs the actual capture by converting surrounded 'O's to 'X's, finalizing the transformation.
if board[r][c] == "O" and (r in [0, ROWS - 1] or c in [0, COLS - 1]):
Check if the current cell is a border 'O'.
Only 'O's on the border or connected to the border are safe; this condition identifies starting points for DFS marking.
if board[r][c] == "O":
Check if the current cell is an unmarked 'O' to be captured.
Only 'O's not marked as safe ('T') should be flipped to 'X', capturing surrounded regions.
board[r][c] = "O"
Restore the marked cell back to 'O'.
This final step cleans up the board, ensuring the safe regions are correctly represented as 'O's in the output.
return
Return immediately if base case conditions are met.
Early return avoids unnecessary recursion and stack overflow by pruning invalid or already processed paths.
capture(r + 1, c)
Recursively call DFS on the cell below.
Exploring downward neighbors ensures all connected 'O's are marked as safe, fully traversing the connected region.
capture(r - 1, c)
Recursively call DFS on the cell above.
Exploring upward neighbors complements the traversal, ensuring no connected safe 'O' is missed.
capture(r, c + 1)
Recursively call DFS on the cell to the right.
Exploring right neighbors completes the four-directional search for connected safe 'O's.
capture(r, c - 1)
Recursively call DFS on the cell to the left.
Exploring left neighbors ensures full coverage of the connected safe region.
if board[r][c] == "T":
Check if the current cell is marked as safe ('T').
Identifies cells that were temporarily marked to distinguish safe 'O's during processing.
def capture(r, c):
Define the recursive DFS function to mark safe 'O's.
Encapsulating the DFS logic in a helper function allows clean recursive exploration of connected 'O's starting from the border.
for r in range(ROWS):
Iterate over each row index.
This loop initiates the process of scanning the board's border rows and columns for 'O's to start DFS.
for c in range(COLS):
Iterate over each column index.
Nested loop allows scanning every cell in the board to identify border 'O's.
for r in range(ROWS):
Iterate over each row index for flipping phase.
This loop prepares to scan the entire board to flip captured 'O's to 'X's.
for c in range(COLS):
Iterate over each column index for flipping phase.
Nested loop ensures every cell is checked for flipping.
for r in range(ROWS):
Iterate over each row index for restoration phase.
This loop prepares to restore all temporarily marked safe cells back to 'O'.
for c in range(COLS):
Iterate over each column index for restoration phase.
Nested loop ensures all marked cells are restored.
ROWS, COLS = len(board), len(board[0])
Calculate the number of rows and columns in the board.
Caching the dimensions avoids repeated calls to len() and simplifies boundary checks throughout the algorithm.
Test Your Understanding
Why does starting DFS from the border 'O's guarantee that all safe 'O's are marked correctly?
Because any 'O' not connected to the border is surrounded by 'X's and should be captured, while all 'O's connected to the border form safe regions. DFS from the border explores all connected 'O's, marking them as safe, ensuring no safe 'O' is missed.
Related Problems
DFS pattern
Don't just read it. Drill it.
Reconstruct Surrounded Regions from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.
Unlock the Surrounded Regions drill