Exercise contract#
Given a rectangular integer grid containing 0=open and 1=blocked, a valid open start [row,col], and zero or more declared valid open exits, return the fewest four-direction moves from start to any exit. Return -1 when no exit is declared or every exit is unreachable. If start is an exit, return 0. Reject an empty or ragged grid, nonbinary cells, and any out-of-bounds or blocked start/exit coordinate. Do not mutate grid.
Constraints
- 1 <= rows, columns and rows*columns <= 200,000.
- Every row has the same positive length and every cell is exactly 0 or 1.
- start contains exactly two in-bounds coordinates and names an open cell.
- Every exit contains exactly two in-bounds coordinates and names an open cell; duplicate exits are allowed and normalized.
- Only up, down, left, and right moves are legal; each move costs one.
Examples
grid=[[0,0,1],[1,0,0],[0,0,0]], start=[2,0], exits=[[0,0],[2,2]]- 2The exit at [2,2] is reached along [2,0]->[2,1]->[2,2].
grid=[[0]], start=[0,0], exits=[[0,0]]- 0The valid open start already belongs to the declared exit set.
Derive the approach#
Baseline: Run a separate DFS from the start for each exit or enumerate simple paths and keep the shortest. Repeated traversals duplicate work, while DFS discovery order does not guarantee the fewest edges without exhaustive search.
- Treat each open cell as a graph vertex with edges to its legal open four-direction neighbors.
- BFS processes vertices in nondecreasing move distance from start, so the first dequeued declared exit has minimum distance.
- Store row, column, and distance in the queue, or track distance by levels. Mark start visited before enqueueing it.
- For every legal open unseen neighbor, mark it visited immediately and enqueue it with distance+1. Enqueue-time marking prevents duplicate frontier entries.
- If the queue empties before reaching an exit, return -1. This is a valid unreachable result, distinct from invalid input.
- Time
- O(rows * columns + number of exits) after input validation.
- Space
- O(rows * columns + number of exits) for visited state, queue, and normalized exit membership.
- Assumptions
- The grid is rectangular and static; hash-set point membership is expected O(1); each cell has at most four neighbors and distances fit a signed integer because rows*columns <= 200,000.
Three-stage hint ladder#
Reveal only the smallest useful step. Any revealed hint should be recorded as assistance.
Reveal hint 1
Observation: every legal move has equal cost, so process cells by distance layer rather than exploring one path to its end.
Reveal hint 2
Invariant/data structure: a FIFO queue holds discovered open cells; mark each cell visited at enqueue time so it enters once.
Reveal hint 3
Transition/pseudocode: dequeue (row,col,d), return d if it is an exit, otherwise enqueue every in-bounds open unseen four-neighbor with d+1; return -1 if exhausted.
Reference solutions#
Open these after an attempt or when working in Learn mode. Viewing either solution is not independent-success evidence.
Reveal tested Python reference
from collections import deque
def nearest_exit(grid, start, exits):
if not grid or not grid[0] or any(len(row) != len(grid[0]) for row in grid):
raise ValueError("grid must be nonempty and rectangular")
if any(cell not in (0, 1) for row in grid for cell in row):
raise ValueError("cells must be 0 or 1")
rows, cols = len(grid), len(grid[0])
def valid_open(point):
return (len(point) == 2 and 0 <= point[0] < rows and 0 <= point[1] < cols
and grid[point[0]][point[1]] == 0)
if not valid_open(start) or any(not valid_open(exit_point) for exit_point in exits):
raise ValueError("start and exits must be in-bounds open cells")
targets = {tuple(exit_point) for exit_point in exits}
queue = deque([(start[0], start[1], 0)])
seen = {(start[0], start[1])}
while queue:
row, col, distance = queue.popleft()
if (row, col) in targets:
return distance
for row_step, col_step in ((1,0),(-1,0),(0,1),(0,-1)):
next_row, next_col = row + row_step, col + col_step
if (0 <= next_row < rows and 0 <= next_col < cols
and grid[next_row][next_col] == 0
and (next_row, next_col) not in seen):
seen.add((next_row, next_col))
queue.append((next_row, next_col, distance + 1))
return -1Reveal tested Java reference
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
final class TransferT08 {
record Point(int row, int col) {}
record State(int row, int col, int distance) {}
static int nearestExit(int[][] grid, Point start, List<Point> exits) {
if (grid.length == 0 || grid[0].length == 0) throw new IllegalArgumentException("empty grid");
int rows = grid.length;
int cols = grid[0].length;
for (int[] row : grid) {
if (row.length != cols) throw new IllegalArgumentException("ragged grid");
for (int cell : row) if (cell != 0 && cell != 1) throw new IllegalArgumentException("nonbinary cell");
}
if (!validOpen(grid, start)) throw new IllegalArgumentException("invalid start");
for (Point exit : exits) if (!validOpen(grid, exit)) throw new IllegalArgumentException("invalid exit");
Set<Point> targets = new HashSet<>(exits);
boolean[][] seen = new boolean[rows][cols];
Queue<State> queue = new ArrayDeque<>();
seen[start.row()][start.col()] = true;
queue.add(new State(start.row(), start.col(), 0));
int[][] directions = {{1,0},{-1,0},{0,1},{0,-1}};
while (!queue.isEmpty()) {
State state = queue.remove();
if (targets.contains(new Point(state.row(), state.col()))) return state.distance();
for (int[] direction : directions) {
int row = state.row() + direction[0];
int col = state.col() + direction[1];
if (row >= 0 && row < rows && col >= 0 && col < cols && grid[row][col] == 0 && !seen[row][col]) {
seen[row][col] = true;
queue.add(new State(row, col, state.distance() + 1));
}
}
}
return -1;
}
private static boolean validOpen(int[][] grid, Point point) {
return point.row() >= 0 && point.row() < grid.length && point.col() >= 0
&& point.col() < grid[0].length && grid[point.row()][point.col()] == 0;
}
}Deterministic test plan#
| Case | Input | Expected | What it catches |
|---|---|---|---|
| worked-example | grid=[[0,0,1],[1,0,0],[0,0,0]], start=[2,0], exits=[[0,0],[2,2]] | 2 | Choose the nearer of multiple exits. |
| start-is-exit | grid=[[0]], start=[0,0], exits=[[0,0]] | 0 | Zero-move result. |
| no-exits | grid=[[0,0],[0,0]], start=[0,0], exits=[] | -1 | Valid empty target set. |
| unreachable | grid=[[0,1,0],[1,1,0],[0,0,0]], start=[0,0], exits=[[0,2]] | -1 | Queue exhaustion. |
| blocked-neighbor-boundaries | grid=[[0,1],[0,0]], start=[0,0], exits=[[1,1]] | 2 | Bounds and blocked cells around the frontier. |
| duplicate-exit | grid=[[0,0]], start=[0,0], exits=[[0,1],[0,1]] | 1 | Normalize target membership. |
| empty-grid | grid=[], start=[0,0], exits=[] | ValueError / IllegalArgumentException | Reject missing rectangle. |
| ragged-grid | grid=[[0],[0,1]], start=[0,0], exits=[] | ValueError / IllegalArgumentException | Reject inconsistent row lengths. |
| nonbinary-cell | grid=[[0,2]], start=[0,0], exits=[] | ValueError / IllegalArgumentException | Reject unsupported cell state. |
| blocked-start | grid=[[1]], start=[0,0], exits=[] | ValueError / IllegalArgumentException | Start must be open. |
| blocked-exit | grid=[[0,1]], start=[0,0], exits=[[0,1]] | ValueError / IllegalArgumentException | Every declared exit must be open. |
Transfer and follow-ups#
- Return the chosen exit as well as distance; define a tie rule for exits reached in the same BFS layer.
- Allow weighted terrain and explain why ordinary FIFO BFS no longer establishes minimum cost.
- Use several starts and show how multi-source BFS changes only initialization, not the invariant.
Transfer enqueue-time visited marking and distance layers to a multi-source grid traversal.
Authorship and review#
Engineering Foundry authored the grid contract, examples, tests, and reference programs. It is not attributed to an employer; four-direction BFS shortest-path reasoning is established algorithmic material.
Engineering Foundry original · Codex self-review on 2026-09-05 · independent human editorial review is not claimed.
Evidence IDs: RA-DSA-CURRICULUM-EXPANSION-2026-09, SRC-DSA-MIT-6006