Capture Enclosed O Regions in an X/O Grid Using Union-Find Instead of Flood Fill
Company: Apple
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
You are given an `m × n` board in which every cell is `'X'` or `'O'`. Two `'O'` cells are connected when they share an edge (up, down, left or right), and a region is a maximal group of connected `'O'` cells. A region is **enclosed** when none of its cells lies on the outer border of the board, that is, in the first or last row or the first or last column. Every cell of an enclosed region is captured and becomes `'X'`; regions with at least one border cell stay as they are.
Return the board after every enclosed region has been captured.
In the interview, this was solved with a disjoint-set (union-find) structure: a skeleton with `find` and `union` was provided to complete, and depth-first or breadth-first flood fill was not allowed. Practice it the same way; the grader checks only the returned board.
### Function Signature
```python
def capture_enclosed_regions(board: list[list[str]]) -> list[list[str]]:
```
### Rules
- Connectivity is 4-directional; cells that touch only diagonally are not connected.
- `'X'` cells never change, and an `'O'` cell changes only if its whole region is enclosed.
- A board with one row or one column has every cell on the border, so it is returned unchanged.
- You may modify `board` in place, but you must return the resulting board, with the same dimensions as the input.
### Constraints
- `1 <= m, n <= 200`, where `m = len(board)` and `n = len(board[0])`
- Every row has exactly `n` cells
- Every cell is the one-character string `'X'` or `'O'`
### Examples
**Example 1**
```text
Input: board = [["X","X","X","X","X"],
["X","O","O","X","X"],
["X","X","O","X","O"],
["X","O","X","X","X"]]
Output: [["X","X","X","X","X"],
["X","X","X","X","X"],
["X","X","X","X","O"],
["X","O","X","X","X"]]
```
The region `(1, 1)`, `(1, 2)`, `(2, 2)` has no border cell, so it is captured. The cell `(2, 4)` is in the last column and `(3, 1)` is in the last row, so both stay.
**Example 2**
```text
Input: board = [["O","X","X"],
["X","O","X"],
["X","X","X"]]
Output: [["O","X","X"],
["X","X","X"],
["X","X","X"]]
```
The cell `(1, 1)` touches `(0, 0)` only diagonally, so it forms its own enclosed region and is captured. The cell `(0, 0)` is on the border and stays.
**Example 3**
```text
Input: board = [["X","X","X","X","X"],
["X","O","O","O","X"],
["X","O","X","O","X"],
["X","O","O","O","X"],
["X","X","X","O","X"]]
Output: [["X","X","X","X","X"],
["X","O","O","O","X"],
["X","O","X","O","X"],
["X","O","O","O","X"],
["X","X","X","O","X"]]
```
The ring of `'O'` cells is one region, and it reaches the bottom row through `(4, 3)`, so the whole ring stays even though most of its cells are interior.
Overview: Given a grid of X and O cells, capture every region of edge-connected O cells that does not touch the border by turning it into X, using a disjoint-set (union-find) structure instead of flood fill. Tests union-find implementation, mapping grid cells to set indices and correct border handling.
Read the full Apple Software Engineer interview experience this question came from
You are given an `m x n` board in which every cell is `'X'` or `'O'`. Two `'O'` cells are connected when they share an edge (up, down, left or right); cells that touch only diagonally are not connected. A region is a maximal group of connected `'O'` cells. A region is **enclosed** when none of its cells lies on the outer border of the board, that is, in the first or last row or the first or last column. Every cell of an enclosed region is captured and becomes `'X'`; a region with at least one border cell stays exactly as it is.
Implement `capture_enclosed_regions(board)` and return the board after every enclosed region has been captured.
**Rules**
- Connectivity is 4-directional; cells that touch only diagonally are not connected.
- `'X'` cells never change, and an `'O'` cell changes only if its whole region is enclosed.
- A board with one row or one column has every cell on the border, so it is returned unchanged.
- You may modify `board` in place, but you must return the resulting board, with the same dimensions as the input.
- In the original interview this was solved with a disjoint-set (union-find) structure: a skeleton with `find` and `union` was provided to complete, and depth-first or breadth-first flood fill was not allowed. Practice it the same way; the grader checks only the returned board.
**Constraints**
- `1 <= m, n <= 200`, where `m = len(board)` and `n = len(board[0])`
- Every row has exactly `n` cells
- Every cell is the one-character string `'X'` or `'O'`
No value involved exceeds 2^31 - 1 (a board has at most 40,000 cells), so 32-bit integers suffice in every language.
**Example 1**
```text
Input: board = [['X','X','X','X','X'],
['X','O','O','X','X'],
['X','X','O','X','O'],
['X','O','X','X','X']]
Output: [['X','X','X','X','X'],
['X','X','X','X','X'],
['X','X','X','X','O'],
['X','O','X','X','X']]
```
The region `(1, 1)`, `(1, 2)`, `(2, 2)` has no border cell, so it is captured. The cell `(2, 4)` is in the last column and `(3, 1)` is in the last row, so both stay.
**Example 2**
```text
Input: board = [['X','X','X','X','X'],
['X','O','O','O','X'],
['X','O','X','O','X'],
['X','O','O','O','X'],
['X','X','X','O','X']]
Output: [['X','X','X','X','X'],
['X','O','O','O','X'],
['X','O','X','O','X'],
['X','O','O','O','X'],
['X','X','X','O','X']]
```
The ring of `'O'` cells is one region, and it reaches the bottom row through `(4, 3)`, so the whole ring stays even though most of its cells are interior.
Constraints
- 1 <= m, n <= 200, where m = len(board) and n = len(board[0])
- Every row has exactly n cells
- Every cell is the one-character string 'X' or 'O'
Examples
Input: ([['X', 'X', 'X', 'X', 'X'], ['X', 'O', 'O', 'X', 'X'], ['X', 'X', 'O', 'X', 'O'], ['X', 'O', 'X', 'X', 'X']],)
Expected Output: [['X', 'X', 'X', 'X', 'X'], ['X', 'X', 'X', 'X', 'X'], ['X', 'X', 'X', 'X', 'O'], ['X', 'O', 'X', 'X', 'X']]
Explanation: Source Example 1: region (1,1),(1,2),(2,2) has no border cell and is captured; (2,4) on the last column and (3,1) on the last row stay.
Input: ([['O', 'X', 'X'], ['X', 'O', 'X'], ['X', 'X', 'X']],)
Expected Output: [['O', 'X', 'X'], ['X', 'X', 'X'], ['X', 'X', 'X']]
Explanation: Source Example 2: (1,1) touches the border cell (0,0) only diagonally, so it is its own enclosed region and is captured.
Hints
- A region keeps every one of its cells as soon as any single cell of it lies in the first or last row or column, so the decision is made per region, never per cell.
- Only the four edge-sharing neighbours matter: two 'O' cells that meet only at a corner belong to different regions.
- A region can reach the border through a long winding path, so settle each cell's connectivity completely before deciding whether it is captured.