Quick 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.

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

  1. 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.
  2. Only the four edge-sharing neighbours matter: two 'O' cells that meet only at a corner belong to different regions.
  3. A region can reach the border through a long winding path, so settle each cell's connectivity completely before deciding whether it is captured.

Loading coding console...

Show the approach

Approach

Model the board as a disjoint-set union over the m * n cells plus one virtual BORDER node (index m * n). Scan the board once: for every 'O' cell, union it with BORDER if it lies in row 0, row m - 1, column 0 or column n - 1, and union it with its right and lower neighbours when those are 'O'. Looking only right and down is enough because every edge between two 'O' cells is seen from its upper or left endpoint, and diagonal neighbours are never considered, which matches 4-directional connectivity.

Invariant: unions happen only along edges between two 'O' cells and from border 'O' cells to BORDER. So after the scan, two 'O' cells share a root exactly when they belong to the same region, and an 'O' cell shares BORDER's root exactly when its region contains at least one border cell. A second scan turns every 'O' whose root differs from BORDER's root into 'X' and leaves every other cell untouched, which is precisely the capture rule: whole enclosed regions flip, border-touching regions and all 'X' cells stay.

find uses path halving and union merges by size, so both are iterative and near-constant time; a long winding region causes no recursion-depth problems.

Edge cases: a 1 x 1 board, a single row or column, and 2 x n or m x 2 boards have every cell on the border and come back unchanged; an all-'X' board is unchanged; an all-'O' board is one border-touching region and is unchanged. The input is copied first, so the returned board always has the input's dimensions.

Time complexity:
O(m * n * alpha(m * n)), effectively O(m * n)
Space complexity:
O(m * n)