Problem overview
The method solve receives board, a char[][] of m rows and n columns where every cell is 'X' or 'O'. Two 'O' cells are connected when they are side by side (up, down, left or right, never diagonal). A region is a group of connected 'O' cells. A region is surrounded when none of its cells lies on the edge of the board, that is, in the first or last row or the first or last column. Every surrounded region must be captured: all its cells become 'X'. A region that touches the edge stays as it is. The method returns nothing; board itself must hold the result.
In the first example, the three 'O' cells in the middle form one region with no cell on the edge, so they become 'X', while the 'O' in the last row is on the edge and stays:
before after
XXXX XXXX
XOOX XXXX
XXOX XXXX
XOXX XOXX
A board with a single 'O' keeps it: that cell is on every edge. In the bigger third example, a long region winds down to the last row, so none of it is captured, and the 'O' in the top-left corner stays because it is on the edge itself. Only the lone 'O' in row 3, column 1 has 'X' on all four sides and is captured.
Approach: start from the edge
The direct idea is to take each 'O' and search for a path of 'O' cells to the edge. That can repeat the same work many times: one large region in the middle of the board is searched again from each of its cells, which in the worst case costs on the order of (m · n)² steps.
Turn the question around. Every 'O' on the edge is safe. So is every 'O' connected to a safe one. Start a breadth-first search (BFS) from all the edge 'O' cells at once, and mark every cell it reaches as safe. When the search ends, any 'O' that was never marked has no path of 'O' cells to the edge, so its region is surrounded. One more pass turns those cells into 'X'.
A cell is marked when it joins the queue, not when it leaves it, so it enters the queue at most once. The search uses a queue, not recursion: a single region can snake through the whole board, and a recursive search that deep could exhaust the call stack. With a queue the depth of the search does not matter.
Step by step
- Store the dimensions.
rowsisboard.length, andcolsisboard[0].length, since all rows have the same length. - Create
safe, aboolean[][]of the same size. Java fills it withfalse, which means "not reached yet". - Create the queue as an
ArrayDequedeclared asDeque<int[]>. Each entry is a two-element array: row and column. - Loop over the rows and the columns with two nested
forloops to find the edge cells. - Compute
onBorder: the row is first or last, or the column is first or last, joined with||. - Check for an edge
'O'withonBorder && board[r][c] == 'O'. The single quotes make'O'achar, the type of each cell. - Mark it in
safeand queue it withnew int[]{r, c}. - Define the four moves as
{row change, column change}pairs: up, down, left and right. Each changes only one coordinate, so diagonal cells are never neighbours. - Loop while the queue is not empty. Each turn takes one safe cell and checks its neighbours.
- Take a cell with
polland read its row and column. - Go over the moves and compute
nextRowandnextCol. - Check the neighbour is on the board. Cells on the edge have neighbours outside it, and those are skipped before indexing.
- If the neighbour is an unmarked
'O', mark it and queue it. An'O'next to a safe one is in the same region and so is connected to the edge too. - After the search, loop over the board again and set every
'O'that is not safe to'X'.
The solution in Java
class Solution {
public void solve(char[][] board) {
int rows = board.length;
int cols = board[0].length;
boolean[][] safe = new boolean[rows][cols];
Deque<int[]> queue = new ArrayDeque<>();
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') {
safe[r][c] = true;
queue.add(new int[]{r, c});
}
}
}
int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int row = cell[0];
int col = cell[1];
for (int[] d : directions) {
int nextRow = row + d[0];
int nextCol = col + d[1];
if (nextRow >= 0 && nextRow < rows && nextCol >= 0 && nextCol < cols) {
if (board[nextRow][nextCol] == 'O' && !safe[nextRow][nextCol]) {
safe[nextRow][nextCol] = true;
queue.add(new int[]{nextRow, nextCol});
}
}
}
}
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (board[r][c] == 'O' && !safe[r][c]) {
board[r][c] = 'X';
}
}
}
}
}
Java details: board is an array of char arrays, so assigning board[r][c] = 'X' changes the caller's board; this is why the method can be void. The boolean[][] needs no filling, because new arrays hold false. poll() returns the front element and removes it. The method uses && and ||, which stop evaluating as soon as the result is known, and the bounds test comes before board[nextRow][nextCol], so an off-board index is never read.
Complexity
Each cell enters the queue at most once and looks at four neighbours, and the two passes over the board visit each cell once, so the time is O(m · n). The safe array and the queue take O(m · n) extra space.
The queue approach does not depend on region shape. On a 200 by 200 board with a frame of 'X' around a 198 by 198 block of 'O', the whole block is captured; on a 200 by 200 board of only 'O', every cell is on or connected to the edge and all 40000 stay.
Edge cases and common mistakes
- Treating every non-edge
'O'as captured. That ignores connectivity. On the third example it leaves only the corner and one more'O'instead of keeping the whole winding region:OXXXX XXXXX XXXXX XXXXX XXXOX - Diagonal neighbours. In
XOX / OXO / XOXno'O'is connected to another, and all four are on the edge, so the board does not change. - A board with no edge
'O'. A single'O'inside an'X'frame (XXX / XOX / XXX) is captured. - Marking on removal from the queue. A cell could then be queued several times by different neighbours. Marking on insertion avoids it.
- Checking bounds after indexing. Reading
board[nextRow][nextCol]before the bounds test throwsArrayIndexOutOfBoundsExceptionon the edges. - Recursion on large boards. A deep recursive search may overflow the stack; the queue does not.
Testing
Run against the solution above, these boards, written row by row, give:
| Board | Result |
|---|---|
XXXX / XOOX / XXOX / XOXX | XXXX / XXXX / XXXX / XOXX |
O | O |
OXXXX / XOOOX / XXXOX / XOXOX / XXXOX | OXXXX / XOOOX / XXXOX / XXXOX / XXXOX |
OOO / OXO / OOO | unchanged |
XXX / XOX / XXX | XXX / XXX / XXX |
XOX / OXO / XOX | unchanged |
