Skip to content

Surrounded Regions

Given an m x n board containing 'X' and 'O', capture all regions of 'O' that are surrounded by 'X'.

A region is captured by flipping all 'O's into 'X's in that surrounded region.

InputOutputExplanation
[["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"]]Only the bottom-left O is surrounded
  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 200
  • board[i][j] is 'X' or 'O'
ApproachTimeSpaceNotes
DFSO(m * n)O(m * n)Recursive exploration, intuitive
Union-FindO(m * n * α(n))O(m * n)Disjoint sets, path compression
  • Pick DFS for most intuitive and straightforward solution.
  • Pick Union-Find to understand disjoint set data structure.
Most Intuitive
DFS
O(m * n) time · O(m * n) space
Elegant
Union-Find
O(m * n * α(n)) time · O(m * n) space
★ Recommended

Mark all border-connected ‘O’ regions from all four borders using DFS, then flip remaining ‘O’s to ‘X’s.

⏱ Time O(m * n) Visit each cell once 💾 Space O(m * n) Recursion stack
function solve(board):
rows = len(board)
cols = len(board[0])
// Mark border-connected O's from all borders
for each cell on border:
if cell is 'O':
dfs(board, r, c, rows, cols)
// Flip unmarked O's to X's
for each cell in board:
if cell is 'O':
cell = 'X'
else if cell is '#': // Marked O's
cell = 'O'
surrounded_regions_dfs.py
def solve_dfs(board: list[list[str]]) -> None:
"""
DFS approach - mark border-connected O's, then flip remaining O's to X's.
Time: O(m * n)
Space: O(m * n)
"""
if not board or not board[0]:
return
rows, cols = len(board), len(board[0])
def dfs(r: int, c: int) -> None:
if r < 0 or r >= rows or c < 0 or c >= cols or board[r][c] != 'O':
return
board[r][c] = '#' # Mark as border-connected
dfs(r + 1, c)
dfs(r - 1, c)
dfs(r, c + 1)
dfs(r, c - 1)
# Mark border-connected O's
for i in range(rows):
if board[i][0] == 'O':
dfs(i, 0)
if board[i][cols - 1] == 'O':
dfs(i, cols - 1)
for j in range(cols):
if board[0][j] == 'O':
dfs(0, j)
if board[rows - 1][j] == 'O':
dfs(rows - 1, j)
# Flip remaining O's to X's and restore marked cells
for i in range(rows):
for j in range(cols):
if board[i][j] == 'O':
board[i][j] = 'X'
elif board[i][j] == '#':
board[i][j] = 'O'
✓ Simple

Create a virtual boundary node and union it with all border-connected ‘O’s. Flip ‘O’s that are not connected to boundary.

⏱ Time O(m * n * α(n)) Union operations with path compression 💾 Space O(m * n) Parent array + rank
surrounded_regions_union_find.py
class UnionFind:
def __init__(self, n: int):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x: int) -> int:
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x: int, y: int) -> None:
px, py = self.find(x), self.find(y)
if px == py:
return
if self.rank[px] < self.rank[py]:
px, py = py, px
self.parent[py] = px
if self.rank[px] == self.rank[py]:
self.rank[px] += 1
def solve_union_find(board: list[list[str]]) -> None:
"""
Union-Find approach - unite boundary with border-connected O's.
Time: O(m * n * α(n))
Space: O(m * n)
"""
if not board or not board[0]:
return
rows, cols = len(board), len(board[0])
uf = UnionFind(rows * cols + 1)
boundary = rows * cols
for i in range(rows):
for j in range(cols):
if board[i][j] == 'O':
if i == 0 or i == rows - 1 or j == 0 or j == cols - 1:
uf.union(i * cols + j, boundary)
else:
if i > 0 and board[i - 1][j] == 'O':
uf.union(i * cols + j, (i - 1) * cols + j)
if j > 0 and board[i][j - 1] == 'O':
uf.union(i * cols + j, i * cols + (j - 1))
for i in range(rows):
for j in range(cols):
if uf.find(i * cols + j) != uf.find(boundary) and board[i][j] == 'O':
board[i][j] = 'X'