Skip to content

130. Surrounded Regions (Medium)

Problem

Given an m × n board of 'X' and 'O', capture all regions of 'O' that are 4-directionally surrounded by 'X' (flip them to 'X'). A region is surrounded if no cell in it lies on the border.

Modify the board in place.

Example

  • Input:
    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

LeetCode 130 · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Brute force, DFS from each O to check if surrounded

final class Solution {
func solve(_ board: inout [[Character]]) {
let rows = board.count, cols = board[0].count
var visited = Array(repeating: Array(repeating: false, count: cols), count: rows)
let directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
for startRow in 0..<rows {
for startCol in 0..<cols where board[startRow][startCol] == "O" && !visited[startRow][startCol] {
var stack = [(startRow, startCol)], region: [(Int, Int)] = []
visited[startRow][startCol] = true
var touchesBorder = false
while let (row, col) = stack.popLast() {
region.append((row, col))
if row == 0 || row == rows - 1 || col == 0 || col == cols - 1 { touchesBorder = true }
for (dr, dc) in directions {
let nr = row + dr, nc = col + dc
if nr >= 0 && nr < rows && nc >= 0 && nc < cols && board[nr][nc] == "O" && !visited[nr][nc] {
visited[nr][nc] = true; stack.append((nr, nc))
}
}
}
if !touchesBorder { for (row, col) in region { board[row][col] = "X" } }
}
}
}
}

For each O, DFS to see if the component touches the border. If not, flip them all to X.

Complexity

  • Time: O(mn)O(m*n), driven by total component work across all cells.
  • Space: O(mn)O(m*n) for the visited set and the done set.

Works but it’s awkward to implement the “touches border” flag cleanly.

Approach 2: Reverse DFS from borders (optimal)

Flip the problem: any O that’s reachable from a border O is not surrounded. Temporarily mark those border-connected Os with a sentinel (e.g., 'T'). After the scan, flip every remaining O to X, then flip T back to O.

def solve(board):
if not board: # L1: O(1) guard
return
rows, cols = len(board), len(board[0]) # L2: O(1)
def dfs(r, c):
if not (0 <= r < rows and 0 <= c < cols) or board[r][c] != 'O':
return # L3: O(1) base case
board[r][c] = 'T' # L4: O(1) mark sentinel
dfs(r + 1, c); dfs(r - 1, c); dfs(r, c + 1); dfs(r, c - 1) # L5: recurse 4 neighbors
for c in range(cols):
dfs(0, c); dfs(rows - 1, c) # L6: seed top and bottom rows
for r in range(rows):
dfs(r, 0); dfs(r, cols - 1) # L7: seed left and right cols
for r in range(rows):
for c in range(cols): # L8: O(m*n) final sweep
if board[r][c] == 'O':
board[r][c] = 'X' # L9: O(1) flip interior O
elif board[r][c] == 'T':
board[r][c] = 'O' # L10: O(1) restore border O

Where the time goes, line by line

Variables: m = grid rows, n = grid cols.

LinePer-call costTimes executedContribution
L6 (seed top/bottom)O(1)O(1)2*nO(n)O(n)
L7 (seed left/right)O(1)O(1)2*mO(m)O(m)
L5 (DFS recursion)O(1)O(1) per cellat most m*n totalO(mn)O(m*n) ← dominates
L8-L10 (final sweep)O(1)O(1)m*nO(mn)O(m*n)

Complexity

  • Time: O(mn)O(m*n), driven by L5/L8 (DFS traversal + final sweep).
  • Space: O(mn)O(m*n) recursion stack in the worst case (a board full of O’s causes a chain of depth m*n).

Approach 3: BFS variant of Approach 2

Same idea with a queue, prefer when recursion depth is a concern.

from collections import deque
def solve(board):
if not board: # L1: O(1) guard
return
rows, cols = len(board), len(board[0]) # L2: O(1)
def bfs(start_r, start_c):
if board[start_r][start_c] != 'O':
return # L3: O(1) early exit
q = deque([(start_r, start_c)]) # L4: O(1) init queue
board[start_r][start_c] = 'T' # L5: O(1) mark start
while q: # L6: loop until queue empty
r, c = q.popleft() # L7: O(1) dequeue
for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and board[nr][nc] == 'O':
board[nr][nc] = 'T'
q.append((nr, nc)) # L8: O(1) enqueue neighbor
for c in range(cols):
bfs(0, c); bfs(rows - 1, c) # L9: seed top and bottom rows
for r in range(rows):
bfs(r, 0); bfs(r, cols - 1) # L10: seed left and right cols
for r in range(rows):
for c in range(cols): # L11: O(m*n) final sweep
if board[r][c] == 'O':
board[r][c] = 'X' # L12: O(1)
elif board[r][c] == 'T':
board[r][c] = 'O' # L13: O(1)

Complexity

  • Time: O(mn)O(m*n), driven by BFS traversal + final sweep.
  • Space: O(mn)O(m*n) for the queue in the worst case.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Summary

ApproachTimeSpace
DFS + border-touch flagO(mn)O(m*n)O(mn)O(m*n)
Reverse DFS from bordersO(mn)O(m*n)O(mn)O(m*n)
Reverse BFS from bordersO(mn)O(m*n)O(mn)O(m*n)

Same “reverse direction from boundary” trick as Pacific Atlantic, the cleanest version of the problem.

Test cases

# Quick smoke tests, paste into a REPL or save as test_130.py and run.
# Uses the canonical implementation (Approach 2: reverse DFS from borders).
def solve(board):
if not board:
return
rows, cols = len(board), len(board[0])
def dfs(r, c):
if not (0 <= r < rows and 0 <= c < cols) or board[r][c] != 'O':
return
board[r][c] = 'T'
dfs(r + 1, c); dfs(r - 1, c); dfs(r, c + 1); dfs(r, c - 1)
for c in range(cols):
dfs(0, c); dfs(rows - 1, c)
for r in range(rows):
dfs(r, 0); dfs(r, cols - 1)
for r in range(rows):
for c in range(cols):
if board[r][c] == 'O':
board[r][c] = 'X'
elif board[r][c] == 'T':
board[r][c] = 'O'
def _run_tests():
# Example from the problem statement
b = [['X','X','X','X'],
['X','O','O','X'],
['X','X','O','X'],
['X','O','X','X']]
solve(b)
assert b == [['X','X','X','X'],
['X','X','X','X'],
['X','X','X','X'],
['X','O','X','X']]
# All X's: nothing changes
b2 = [['X','X'],['X','X']]
solve(b2)
assert b2 == [['X','X'],['X','X']]
# Single cell O on border: stays O
b3 = [['O']]
solve(b3)
assert b3 == [['O']]
# O's entirely on the border: all stay
b4 = [['O','O','O'],
['O','X','O'],
['O','O','O']]
solve(b4)
assert b4 == [['O','O','O'],
['O','X','O'],
['O','O','O']]
# Interior O fully surrounded: gets captured
b5 = [['X','X','X'],
['X','O','X'],
['X','X','X']]
solve(b5)
assert b5 == [['X','X','X'],
['X','X','X'],
['X','X','X']]
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Arrays, board with sentinel mutation
  • Flood Fill, the grid expansion pattern for connected cells that share a condition.
  • DFS, the depth first traversal habit of following one branch before returning.