Fill each empty room with the distance to its nearest gate. The trick: instead of running a BFS from every room (slow), launch a single multi-source BFS simultaneously from every gate, so the first time a BFS wave reaches a room it is guaranteed to carry the shortest distance.
You have an m × n grid where each cell is one of three things: a wall (-1), a gate (0), or an empty room (INF = 2147483647). Fill every empty room with the number of steps to its nearest gate. Rooms with no path to any gate stay at INF. Modify the grid in-place.
Concrete example — 4×4 grid:
Input: INF -1 0 INF INF INF INF -1 INF -1 INF -1 0 -1 INF INF Output: 3 -1 0 1 2 2 1 -1 1 -1 2 -1 0 -1 3 4
The gate at [0][2] fills its neighbours first; the gate at [3][0] fills the bottom-left corner. Every room gets the minimum of its distances to both gates.
O(m · n).(r, c) onto the queue for each cell with value 0. This seeds distance-0 for all gates at once.head pointer as a queue. Dequeue (r, c).(nr, nc) that is in bounds and equals INF, set rooms[nr][nc] = rooms[r][c] + 1 and enqueue it.-1) and already-settled rooms are both non-INF, so the single check rooms[nr][nc] !== INF handles both. No visited set needed.INF.INF room and taking the minimum — that is O((m·n)²) in the worst case. Multi-source BFS avoids it by flipping the direction: push the gates, let the rooms come to you. The guard rooms[nr][nc] !== INFalso doubles as the "visited" check, so you do not need a separate boolean matrix.Space is also O(mn) for the queue in the worst case (all cells reachable, all gates in one corner). The input matrix itself is mutated in-place, so no extra grid copy is needed.
4×4 cells. Walls = W, gates = 0, rooms = ∞. We seed the BFS queue with every gate.1▶function wallsAndGates(rooms: number[][]): void {2▶ const INF = 2147483647;3▶ const rows = rooms.length;4▶ const cols = rows > 0 ? rooms[0].length : 0;5▶ const queue: [number, number][] = [];67 // Seed: enqueue every gate (distance 0)8 for (let r = 0; r < rows; r++) {9 for (let c = 0; c < cols; c++) {10 if (rooms[r][c] === 0) queue.push([r, c]);11 }12 }1314 // BFS: each wave is one step farther from the nearest gate15 const DIRS: [number, number][] = [[-1,0],[1,0],[0,-1],[0,1]];16 let head = 0;17 while (head < queue.length) {18 const [r, c] = queue[head++];19 for (const [dr, dc] of DIRS) {20 const nr = r + dr, nc = c + dc;21 if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;22 if (rooms[nr][nc] !== INF) continue; // wall or already settled23 rooms[nr][nc] = rooms[r][c] + 1; // BFS guarantees shortest24 queue.push([nr, nc]);25 }26 }27}
function wallsAndGates(rooms: number[][]): void {
const INF = 2147483647;
const rows = rooms.length;
const cols = rows > 0 ? rooms[0].length : 0;
const queue: [number, number][] = [];
// Seed: enqueue every gate (distance 0)
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (rooms[r][c] === 0) queue.push([r, c]);
}
}
// BFS: each wave is one step farther from the nearest gate
const DIRS: [number, number][] = [[-1,0],[1,0],[0,-1],[0,1]];
let head = 0;
while (head < queue.length) {
const [r, c] = queue[head++];
for (const [dr, dc] of DIRS) {
const nr = r + dr, nc = c + dc;
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;
if (rooms[nr][nc] !== INF) continue; // wall or already settled
rooms[nr][nc] = rooms[r][c] + 1; // BFS guarantees shortest
queue.push([nr, nc]);
}
}
}0. This is the multi-source seed: BFS will now propagate outward from all gates simultaneously, exactly as if they all fired at the same clock tick.head pointer avoids the O(n) cost of shift(). The loop runs until every reachable cell has been settled.(r, c) we check all four directions. The guard rooms[nr][nc] !== INF skips walls (-1) and already-filled rooms in a single comparison — no separate visited set needed. When we write rooms[nr][nc] = rooms[r][c] + 1, BFS distance guarantees this is the shortest path.INF = 2147483647. No second pass is required.O(m · n) — each cell is enqueued and dequeued at most once. Space: O(m · n) for the queue (at most every cell). The grid is modified in-place; no extra matrix needed.INF. Correct by default — no special case needed.INF, run the same BFS writing into it, and return it. Same complexity.O(perimeter) rather than O(mn).DIRS array.function wallsAndGates(rooms: number[][]): void {
const INF = 2147483647;
const rows = rooms.length;
const cols = rows > 0 ? rooms[0].length : 0;
const queue: [number, number][] = [];
// Seed: enqueue every gate (distance 0)
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (rooms[r][c] === 0) queue.push([r, c]);
}
}
// BFS: each wave is one step farther from the nearest gate
const DIRS: [number, number][] = [[-1,0],[1,0],[0,-1],[0,1]];
let head = 0;
while (head < queue.length) {
const [r, c] = queue[head++];
for (const [dr, dc] of DIRS) {
const nr = r + dr, nc = c + dc;
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;
if (rooms[nr][nc] !== INF) continue; // wall or already settled
rooms[nr][nc] = rooms[r][c] + 1; // BFS guarantees shortest
queue.push([nr, nc]);
}
}
}O(m · n) — each cell is enqueued and dequeued at most once. Space: O(m · n) for the queue (at most every cell). The grid is modified in-place; no extra matrix needed.Instead of one wave from all the gates, run an independent BFS outward from each empty room until it reaches the nearest gate, writing that distance back into the cell.
function wallsAndGates(rooms: number[][]): void {
const INF = 2147483647;
const rows = rooms.length;
const cols = rows > 0 ? rooms[0].length : 0;
const DIRS: [number, number][] = [[-1,0],[1,0],[0,-1],[0,1]];
// Shortest distance from (sr, sc) to any gate, or INF if unreachable.
function nearestGate(sr: number, sc: number): number {
const dist: number[][] = Array.from({ length: rows }, () =>
new Array<number>(cols).fill(-1),
);
dist[sr][sc] = 0;
const queue: [number, number][] = [[sr, sc]];
let head = 0;
while (head < queue.length) {
const [r, c] = queue[head++];
if (rooms[r][c] === 0) return dist[r][c]; // hit a gate
for (const [dr, dc] of DIRS) {
const nr = r + dr, nc = c + dc;
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;
if (rooms[nr][nc] === -1) continue; // wall
if (dist[nr][nc] !== -1) continue; // already visited
dist[nr][nc] = dist[r][c] + 1;
queue.push([nr, nc]);
}
}
return INF;
}
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (rooms[r][c] === INF) {
rooms[r][c] = nearestGate(r, c);
}
}
}
}O((m·n)²). The multi-source BFS flips it around: seed the queue with all gates at distance 0 and expand once, so every cell is settled exactly once in O(m·n).| "fill each room with distance to nearest gate/source" | multi-source BFS |
| minimum distance from a set (not a single node) | seed queue with the whole set |
| rooms[nr][nc] !== INF as visited check | overwrite with distance; wall = -1 also blocked |
| "rotting oranges / 01 matrix / Pacific Atlantic" | same multi-source BFS template |
const INF = 2147483647;
const queue: [number, number][] = [];
// 1. seed: every gate
for each cell if rooms[r][c] === 0: queue.push([r,c]);
// 2. BFS wave
let head = 0;
while (head < queue.length) {
const [r, c] = queue[head++];
for (const [dr,dc] of DIRS) {
if (in-bounds && rooms[nr][nc] === INF) {
rooms[nr][nc] = rooms[r][c] + 1;
queue.push([nr, nc]);
}
}
}rooms[nr][nc] !== INF serves which two purposes at once?