← Back to DevBytes

Surrounded Regions: Multiple Solutions and Complexity Analysis

Surrounded Regions: Multiple Solutions and Complexity Analysis

The Surrounded Regions problem is a classic graph traversal challenge frequently encountered in coding interviews and competitive programming. Given a 2D board containing the characters 'X' and 'O', the goal is to capture every region of 'O's that is fully surrounded by 'X's by flipping those 'O's into 'X's. A region is considered surrounded if none of its 'O' cells lie on the border of the board, since border cells cannot be enclosed on all four sides.

This tutorial walks through the problem in detail, presents multiple working solutions, analyzes their time and space complexity, and highlights best practices you can apply to similar grid-based problems.

Problem Statement

You are given an m x n matrix board containing 'X' and 'O'. Capture all regions that are 4-directionally surrounded by 'X'. A region is captured by flipping all 'O's into 'X's within that surrounded region. You must modify the board in-place.

Connectivity is defined in the four cardinal directions: up, down, left, and right. Any 'O' that is connected (directly or transitively) to a border 'O' is not surrounded and must remain unchanged.

Why This Problem Matters

Key Insight

Instead of trying to find surrounded regions directly (which is hard because you must verify enclosure on all sides), flip the problem around: find the regions that are NOT surrounded. Any 'O' connected to a border cell is safe. Once you mark those, every remaining 'O' must be surrounded and can be flipped to 'X'.

This transforms the problem into a graph reachability question starting from all border 'O' cells.

Solution 1: Depth-First Search from the Border

The DFS approach iterates over every border cell. Whenever it finds an 'O', it launches a DFS that marks all connected 'O' cells with a temporary marker (e.g., '#'). After the traversal, the board is cleaned up: '#' becomes 'O' (safe), and every remaining 'O' becomes 'X' (captured).

Implementation

public class SurroundedRegionsDFS {
    public void solve(char[][] board) {
        if (board == null || board.length == 0) return;
        int rows = board.length;
        int cols = board[0].length;

        // Mark all border-connected 'O's with '#'
        for (int r = 0; r < rows; r++) {
            if (board[r][0] == 'O') dfs(board, r, 0);
            if (board[r][cols - 1] == 'O') dfs(board, r, cols - 1);
        }
        for (int c = 0; c < cols; c++) {
            if (board[0][c] == 'O') dfs(board, 0, c);
            if (board[rows - 1][c] == 'O') dfs(board, rows - 1, c);
        }

        // Final pass: '#' -> 'O', 'O' -> 'X'
        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] == '#') board[r][c] = 'O';
            }
        }
    }

    private void dfs(char[][] board, int r, int c) {
        int rows = board.length;
        int cols = board[0].length;
        if (r < 0 || r >= rows || c < 0 || c >= cols) return;
        if (board[r][c] != 'O') return;

        board[r][c] = '#'; // mark as safe
        dfs(board, r + 1, c);
        dfs(board, r - 1, c);
        dfs(board, r, c + 1);
        dfs(board, r, c - 1);
    }
}

Complexity Analysis

Solution 2: Breadth-First Search (Iterative)

BFS avoids the recursion depth issue entirely by using an explicit queue. The logic is identical to DFS—start from border 'O's and mark reachable cells—but the traversal order changes. BFS is generally safer for very large boards.

Implementation

import java.util.LinkedList;
import java.util.Queue;

public class SurroundedRegionsBFS {
    public void solve(char[][] board) {
        if (board == null || board.length == 0) return;
        int rows = board.length;
        int cols = board[0].length;
        Queue queue = new LinkedList<>();

        // Enqueue all border 'O's
        for (int r = 0; r < rows; r++) {
            if (board[r][0] == 'O') { board[r][0] = '#'; queue.offer(new int[]{r, 0}); }
            if (board[r][cols - 1] == 'O') { board[r][cols - 1] = '#'; queue.offer(new int[]{r, cols - 1}); }
        }
        for (int c = 0; c < cols; c++) {
            if (board[0][c] == 'O') { board[0][c] = '#'; queue.offer(new int[]{0, c}); }
            if (board[rows - 1][c] == 'O') { board[rows - 1][c] = '#'; queue.offer(new int[]{rows - 1, c}); }
        }

        int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
        while (!queue.isEmpty()) {
            int[] cell = queue.poll();
            for (int[] d : dirs) {
                int nr = cell[0] + d[0];
                int nc = cell[1] + d[1];
                if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && board[nr][nc] == 'O') {
                    board[nr][nc] = '#';
                    queue.offer(new int[]{nr, nc});
                }
            }
        }

        // Final cleanup
        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] == '#') board[r][c] = 'O';
            }
        }
    }
}

Complexity Analysis

Solution 3: Union-Find (Disjoint Set Union)

Union-Find offers an elegant alternative. The idea is to introduce a virtual "dummy" node representing the outside of the board. Every border 'O' is unioned with the dummy node. Every interior 'O' is unioned with its adjacent 'O' neighbors. After processing, any 'O' that shares a root with the dummy node is safe; all others are captured.

Implementation

public class SurroundedRegionsUF {
    private int[] parent;
    private int[] rank;

    public void solve(char[][] board) {
        if (board == null || board.length == 0) return;
        int rows = board.length;
        int cols = board[0].length;
        int dummy = rows * cols;
        parent = new int[rows * cols + 1];
        rank = new int[rows * cols + 1];
        for (int i = 0; i <= dummy; i++) parent[i] = i;

        int[][] dirs = {{1, 0}, {0, 1}}; // right and down are enough to connect neighbors

        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                if (board[r][c] != 'O') continue;
                int idx = r * cols + c;
                if (r == 0 || r == rows - 1 || c == 0 || c == cols - 1) {
                    union(idx, dummy);
                }
                for (int[] d : dirs) {
                    int nr = r + d[0];
                    int nc = c + d[1];
                    if (nr < rows && nc < cols && board[nr][nc] == 'O') {
                        union(idx, nr * cols + nc);
                    }
                }
            }
        }

        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                if (board[r][c] == 'O' && find(r * cols + c) != find(dummy)) {
                    board[r][c] = 'X';
                }
            }
        }
    }

    private int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }

    private void union(int x, int y) {
        int rx = find(x);
        int ry = find(y);
        if (rx == ry) return;
        if (rank[rx] < rank[ry]) parent[rx] = ry;
        else if (rank[rx] > rank[ry]) parent[ry] = rx;
        else { parent[ry] = rx; rank[rx]++; }
    }
}

Complexity Analysis

Union-Find is slightly more complex to implement but shines when the problem evolves into dynamic connectivity queries—for example, if cells could be added or flipped incrementally and you needed to answer "is this cell safe?" repeatedly.

Comparing the Solutions

Best Practices

Common Pitfalls

Conclusion

The Surrounded Regions problem is a deceptively simple exercise that rewards careful thinking about graph reachability and border conditions. By reframing the question from "find surrounded regions" to "find regions connected to the border," you unlock clean, efficient solutions using DFS, BFS, or Union-Find. BFS is the most robust choice for general use due to its immunity to stack overflow, while Union-Find offers extensibility for more complex variants. Mastering this problem builds intuition that transfers directly to other grid-based graph challenges such as number of islands, walls and gates, and pacific atlantic water flow.

🛠 Tools from DevBytes

Inventory Tracker Pro — Excel inventory system, low-stock alerts · $19
AI Dev Kit for Mac — local AI dev environment templates · $9.99
KeyMapper for Mac — custom keyboard shortcut toolkit · $7.99

← Back to all articles