84.Surrounded Regions
Problem
You are given an m x n board of characters 'X' and 'O'. A region is a group of 'O' cells connected horizontally or vertically. A region is surrounded if none of its cells lies on the border of the board — it is completely enclosed by 'X' cells.
Capture every surrounded region by flipping all of its 'O' cells to 'X', in place. Regions that touch the border stay unchanged. The method returns nothing.
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 region {(1,1),(1,2),(2,2)} never reaches the border, so it is captured. (3,1) sits on the bottom border, so it survives.Input: board = [["X"]]
Output: [["X"]]
Explanation: There is no 'O' to capture.Input: board = [
["O","O"],
["O","O"]
]
Output: [
["O","O"],
["O","O"]
]
Explanation: Every cell is on the border, so nothing is surrounded.Constraints
m == board.length,n == board[i].length1 <= m, n <= 200board[i][j]is'X'or'O'
💡 Key Insight
Deciding "is this region enclosed?" region by region is awkward. The complement is easy: an 'O' survives exactly when it is connected to a border 'O'. So flood-fill from every 'O' on the border and temporarily mark everything reached as safe ('T'). Afterwards, any 'O' left unmarked must be enclosed — flip it to 'X' — and turn every 'T' back into 'O'. Two linear passes, O(m·n) total.
Brute Force
TimeO((m·n)²)SpaceO(m·n)Intuition
For each 'O', explore its whole region with a fresh BFS and check whether any cell of that region lies on the border. If none does, this cell must be captured. Record the decision in a separate matrix and apply all flips at the end, so the checks are not affected by flips made along the way.
Algorithm
- Create
capture[m][n]. - For every cell with
board[r][c] == 'O':- BFS over
'O'neighbours with a freshvisitedarray. - If any reached cell has
r == 0,r == m - 1,c == 0orc == n - 1, the cell is safe; otherwise setcapture[r][c] = true.
- BFS over
- Flip every cell with
capture[r][c] == trueto'X'.
Visualization
X X X X
X O O X cell (1,1): BFS reaches (1,1),(1,2),(2,2) → no border cell → capture
X X O X cell (1,2): same BFS again → capture
X O X X cell (2,2): same BFS again → capture
cell (3,1): it IS on the border → safeCode
import java.util.ArrayDeque;
import java.util.Deque;
class Solution {
private static final int[][] DIRS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public void solve(char[][] board) {
int rows = board.length, cols = board[0].length;
boolean[][] capture = new boolean[rows][cols];
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (board[r][c] == 'O' && !touchesBorder(board, r, c)) {
capture[r][c] = true;
}
}
}
// Apply flips only after every decision is made
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (capture[r][c]) board[r][c] = 'X';
}
}
}
// Fresh BFS over the 'O' region containing (r, c)
private boolean touchesBorder(char[][] board, int r, int c) {
int rows = board.length, cols = board[0].length;
boolean[][] visited = new boolean[rows][cols];
Deque<int[]> queue = new ArrayDeque<>();
queue.offer(new int[]{r, c});
visited[r][c] = true;
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int cr = cell[0], cc = cell[1];
if (cr == 0 || cr == rows - 1 || cc == 0 || cc == cols - 1) {
return true;
}
for (int[] d : DIRS) {
int nr = cr + d[0], nc = cc + d[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
&& !visited[nr][nc] && board[nr][nc] == 'O') {
visited[nr][nc] = true;
queue.offer(new int[]{nr, nc});
}
}
}
return false;
}
}Complexity
- Time: O((m·n)²) — each
'O'can trigger a BFS over the whole board. - Space: O(m·n) —
capture,visitedand the queue.
DFS from the Border
TimeO(m·n)SpaceO(m·n)Intuition
Reverse the question. Walk the four edges; whenever an edge cell is 'O', DFS through its region and mark every cell 'T' (temporarily safe). Any 'O' not reached this way cannot reach the border, so it is surrounded. A final sweep flips 'O' → 'X' and 'T' → 'O'.
Algorithm
- For each row
r:dfs(r, 0)anddfs(r, n - 1). - For each column
c:dfs(0, c)anddfs(m - 1, c). dfs(r, c): if out of bounds orboard[r][c] != 'O', return; else set'T'and recurse on four neighbours.- Sweep the board:
'O'becomes'X','T'becomes'O'.
Dry Run
start after border DFS final sweep
X X X X X X X X X X X X
X O O X X O O X X X X X
X X O X → X X O X → X X X X
X O X X X T X X X O X X
Only (3,1) is a border 'O'; its region is just itself → marked 'T'.
The inner region is still 'O' → flipped to 'X'; 'T' restored to 'O'.Code
class Solution {
public void solve(char[][] board) {
int rows = board.length, cols = board[0].length;
// Mark every 'O' connected to the border as safe ('T')
for (int r = 0; r < rows; r++) {
dfs(board, r, 0);
dfs(board, r, cols - 1);
}
for (int c = 0; c < cols; c++) {
dfs(board, 0, c);
dfs(board, rows - 1, c);
}
// Remaining 'O's are surrounded; safe cells go back to 'O'
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (board[r][c] == 'O') board[r][c] = 'X';
else if (board[r][c] == 'T') board[r][c] = 'O';
}
}
}
private void dfs(char[][] board, int r, int c) {
if (r < 0 || r >= board.length || c < 0 || c >= board[0].length || board[r][c] != 'O') {
return;
}
board[r][c] = 'T';
dfs(board, r + 1, c);
dfs(board, r - 1, c);
dfs(board, r, c + 1);
dfs(board, r, c - 1);
}
}Complexity
- Time: O(m·n) — every cell is marked at most once and the final sweep is linear.
- Space: O(m·n) — recursion depth when a single border region snakes through the whole board.
BFS from the BorderOptimal
TimeO(m·n)SpaceO(m·n)Intuition
Identical logic, but the border regions are explored with one multi-source BFS: push every border 'O' (already marked 'T') into a queue and spread to neighbouring 'O' cells. The queue replaces the call stack, so a 200 x 200 board full of 'O' cannot overflow it.
Algorithm
- For every border cell that is
'O': mark it'T'and enqueue it. - While the queue is non-empty: pop a cell; for each in-bounds neighbour equal to
'O', mark it'T'and enqueue it. - Sweep:
'O' → 'X','T' → 'O'.
Dry Run
flowchart TD
A["scan border cells"] --> B{"border cell is 'O'?"}
B -->|"yes: (3,1)"| C["mark 'T', enqueue"]
B -->|"no"| A
C --> D["pop (3,1)<br/>neighbour (2,1) is 'X'"]
D --> E["queue empty"]
E --> F["sweep: (1,1),(1,2),(2,2) are 'O'<br/>→ flip to 'X'"]
F --> G["sweep: (3,1) is 'T'<br/>→ restore to 'O'"]Code
import java.util.ArrayDeque;
import java.util.Deque;
class Solution {
private static final int[][] DIRS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public void solve(char[][] board) {
int rows = board.length, cols = board[0].length;
Deque<int[]> queue = new ArrayDeque<>();
// Seed the queue with every border 'O'
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
boolean onBorder = r == 0 || r == rows - 1 || c == 0 || c == cols - 1;
if (onBorder && board[r][c] == 'O') {
board[r][c] = 'T';
queue.offer(new int[]{r, c});
}
}
}
// Spread "safe" to every 'O' connected to the border
while (!queue.isEmpty()) {
int[] cell = queue.poll();
for (int[] d : DIRS) {
int nr = cell[0] + d[0], nc = cell[1] + d[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && board[nr][nc] == 'O') {
board[nr][nc] = 'T';
queue.offer(new int[]{nr, nc});
}
}
}
// Capture enclosed cells, restore safe ones
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (board[r][c] == 'O') board[r][c] = 'X';
else if (board[r][c] == 'T') board[r][c] = 'O';
}
}
}
}Complexity
- Time: O(m·n) — each cell enters the queue at most once, plus one linear sweep.
- Space: O(m·n) — worst-case queue size (the marking itself is done in place).
Complexity Comparison
| Approach | Time | Space |
|---|---|---|
| Brute Force | O((m·n)²) | O(m·n) |
| DFS from Border | O(m·n) | O(m·n) |
| BFS from Border | O(m·n) | O(m·n) |
Common Pitfalls
- Marking safe cells with
'O'or'X'instead of a third character — the final sweep then cannot tell "connected to the border" apart from "enclosed". - Doing the final sweep in the wrong order (
'T' → 'O'first, then'O' → 'X') — that captures the safe cells too. Use oneif / else ifper cell. - Only seeding from the top and left edges; all four edges matter.
- Forgetting single-row or single-column boards, where every cell is on the border and nothing can be captured.
- Treating diagonal
'O's as connected — a region only spreads up, down, left and right.