130. Surrounded Regions - Explanation

Problem Link

Description

You are given a 2-D matrix board containing 'X' and 'O' characters.

If a continous, four-directionally connected group of 'O's is surrounded by 'X's, it is considered to be surrounded.

Change all surrounded regions of 'O's to 'X's and do so in-place by modifying the input board.

Example 1:

Input: board = [
  ["X","X","X","X"],
  ["X","O","O","X"],
  ["X","O","O","X"],
  ["X","X","X","O"]
]

Output: [
  ["X","X","X","X"],
  ["X","X","X","X"],
  ["X","X","X","X"],
  ["X","X","X","O"]
]

Explanation: Note that regions that are on the border are not considered surrounded regions.

Constraints:

  • 1 <= board.length, board[i].length <= 200
  • board[i][j] is 'X' or 'O'.


Topics

Recommended Time & Space Complexity

You should aim for a solution with O(m * n) time and O(m * n) space, where m is the number of rows and n is the number of columns in the matrix.


Hint 1

We observe that we need to capture the regions that are not connected to the O's on the border of the matrix. This means there should be no path connecting the O's on the border to any O's in the region. Can you think of a way to check the region connected to these border O's?


Hint 2

We can use the Depth First Search (DFS) algorithm. Instead of checking the region connected to the border O's, we can reverse the approach and mark the regions that are reachable from the border O's. How would you implement this?


Hint 3

We run the DFS from every 'O' on the border of the matrix, visiting the neighboring cells that are equal to 'O' recursively and marking them as '#' to avoid revisiting. After completing all the DFS calls, we traverse the matrix again and capture the cells where matrix[i][j] == 'O', and unmark the cells back to 'O' where matrix[i][j] == '#'.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Graph Traversal (DFS/BFS) - Exploring connected components in a 2D grid using depth-first or breadth-first search
  • Flood Fill Algorithm - Marking all connected cells starting from a given position
  • Union-Find (Disjoint Set Union) - Alternative approach using DSU to group connected components and check connectivity

Intuition

Only the 'O' regions that touch the border can never be surrounded, because they have a path to the outside of the board.
So instead of trying to find surrounded regions directly, we do the opposite:

  1. Mark all border-connected 'O' cells as “safe” (temporary mark 'T').
  2. Any remaining 'O' is truly surrounded → flip it to 'X'.
  3. Convert the temporary 'T' back to 'O'.

Algorithm

  1. Let ROWS and COLS be board dimensions.
  2. Define capture(r, c) (dfs):
    • If out of bounds or cell is not 'O', return.
    • Mark cell as 'T'.
    • dfs to its 4 neighbors (up, down, left, right).
  3. Run capture from every border cell that is 'O':
    • All cells in first/last column.
    • All cells in first/last row.
  4. Scan entire board:
    • If cell is 'O', it's surrounded → change to 'X'.
    • If cell is 'T', it's safe → change back to 'O'.
class Solution:
    def solve(self, board: List[List[str]]) -> None:
        ROWS, COLS = len(board), len(board[0])

        def capture(r, c):
            if (r < 0 or c < 0 or r == ROWS or
                c == COLS or board[r][c] != "O"
            ):
                return
            board[r][c] = "T"
            capture(r + 1, c)
            capture(r - 1, c)
            capture(r, c + 1)
            capture(r, c - 1)

        for r in range(ROWS):
            if board[r][0] == "O":
                capture(r, 0)
            if board[r][COLS - 1] == "O":
                capture(r, COLS - 1)

        for c in range(COLS):
            if board[0][c] == "O":
                capture(0, c)
            if board[ROWS - 1][c] == "O":
                capture(ROWS - 1, c)

        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"

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity: O(mn)O(m * n)

Where mm is the number of rows and nn is the number of columns of the boardboard.


Intuition

Same idea as DFS, but we use BFS with a queue.

  • Any 'O' that is connected to the border can “escape”, so it should NOT be flipped.
  • Start BFS from all border 'O' cells and mark every reachable 'O' as temporary 'T' (safe).
  • After that:
    • leftover 'O' cells are fully surrounded → flip to 'X'
    • 'T' cells are safe → change back to 'O'

Algorithm

  1. Initialize a queue and push all border cells that contain 'O'.
  2. While the queue is not empty:
    • Pop a cell (r, c)
    • If it is 'O', mark it as 'T'
    • Push its 4 neighbors (up/down/left/right) if they are in bounds
  3. Traverse the entire board:
    • Change 'O''X' (surrounded)
    • Change 'T''O' (safe)
class Solution:
    def solve(self, board: List[List[str]]) -> None:
        ROWS, COLS = len(board), len(board[0])
        directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]

        def capture():
            q = deque()
            for r in range(ROWS):
                for c in range(COLS):
                    if (r == 0 or r == ROWS - 1 or
                        c == 0 or c == COLS - 1 and
                        board[r][c] == "O"
                    ):
                        q.append((r, c))
            while q:
                r, c = q.popleft()
                if board[r][c] == "O":
                    board[r][c] = "T"
                    for dr, dc in directions:
                        nr, nc = r + dr, c + dc
                        if 0 <= nr < ROWS and 0 <= nc < COLS:
                            q.append((nr, nc))

        capture()
        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"

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity: O(mn)O(m * n)

Where mm is the number of rows and nn is the number of columns of the boardboard.


Common Pitfalls

Trying to Find Surrounded Regions Directly

The intuitive approach of finding regions completely surrounded by 'X' is error-prone. A region touching any border cell cannot be captured, and checking this condition during a flood fill is complex. The correct approach is to invert the logic: first mark all border-connected 'O' cells as safe, then flip all remaining 'O' cells. This reversal simplifies the problem significantly.

Forgetting to Check All Four Borders

When marking safe regions, you must start DFS/BFS from 'O' cells on all four borders: top row, bottom row, left column, and right column. A common mistake is only checking two opposite edges (like top and bottom) and missing cells connected through the left or right borders. Ensure your initial seeding loop covers all border cells.

Modifying Cells Without a Temporary Marker

If you flip 'O' to 'X' immediately when you find a surrounded region, you may incorrectly process cells that should remain 'O'. The standard approach uses a temporary marker (like 'T') to distinguish between safe 'O' cells and those to be processed. After marking is complete, convert 'T' back to 'O' and remaining 'O' to 'X' in a final pass.


3. Disjoint Set Union

Intuition

Treat every 'O' cell as a node in a graph. Two 'O' cells belong to the same region if they are 4-directionally connected.

The key observation:

  • Any region of 'O' that touches the border is safe (it cannot be surrounded).
  • Any region of 'O' that does not touch the border is captured → should become 'X'.

So we use DSU (Union-Find) to group connected 'O' cells, and we create one extra dummy node that represents "connected to border".

  • Union every border 'O' with the dummy node.
  • Union every 'O' with its neighboring 'O' cells.
  • Finally, any cell not connected to the dummy node is surrounded → flip to 'X'.

Algorithm

  1. Create a dsu for (ROWS * COLS) cells plus 1 dummy node.
  2. For each cell (r, c):
    • If it is not 'O', skip.
    • Convert (r, c) to an id: id = r * COLS + c.
    • If (r, c) is on the border, union id with dummy.
    • Union id with any 4-direction neighbor that is also 'O'.
  3. Traverse the grid again:
    • If a cell is 'O' but not connected to dummy, flip it to 'X'.
    • Otherwise keep it as 'O'.
class DSU:
    def __init__(self, n):
        self.Parent = list(range(n + 1))
        self.Size = [1] * (n + 1)

    def find(self, node):
        if self.Parent[node] != node:
            self.Parent[node] = self.find(self.Parent[node])
        return self.Parent[node]

    def union(self, u, v):
        pu = self.find(u)
        pv = self.find(v)
        if pu == pv:
            return False
        if self.Size[pu] >= self.Size[pv]:
            self.Size[pu] += self.Size[pv]
            self.Parent[pv] = pu
        else:
            self.Size[pv] += self.Size[pu]
            self.Parent[pu] = pv
        return True

    def connected(self, u, v):
        return self.find(u) == self.find(v)

class Solution:
    def solve(self, board: List[List[str]]) -> None:
        ROWS, COLS = len(board), len(board[0])
        directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
        dsu = DSU(ROWS * COLS + 1)

        for r in range(ROWS):
            for c in range(COLS):
                if board[r][c] != "O":
                    continue
                if (r == 0 or c == 0 or
                    r == (ROWS - 1) or c == (COLS - 1)
                ):
                    dsu.union(ROWS * COLS, r * COLS + c)
                else:
                    for dx, dy in directions:
                        nr, nc = r + dx, c + dy
                        if board[nr][nc] == "O":
                            dsu.union(r * COLS + c, nr * COLS + nc)

        for r in range(ROWS):
            for c in range(COLS):
                if not dsu.connected(ROWS * COLS, r * COLS + c):
                    board[r][c] = "X"

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity: O(mn)O(m * n)

Where mm is the number of rows and nn is the number of columns of the boardboard.