Rot spreads simultaneously from every infected orange at once — not one-at-a-time. The key is multi-source BFS: seed the queue with all rotten cells before the first step, then count BFS layers as minutes.
Given an m × n grid where 0 = empty, 1 = fresh orange, 2 = rotten orange: every minute, any fresh orange 4-directionally adjacent to a rotten one becomes rotten. Return the minimum minutes until no fresh oranges remain, or -1 if impossible.
Worked example. Grid:
[[2,1,1], [1,1,0], [0,1,1]]
Minute 1: cells (0,1), (1,0) rot. Minute 2: (0,2), (1,1) rot. Minute 3: (2,1) rots. Minute 4: (2,2) rots. Answer: 4.
2-cells into a queue; count freshcells. This is the BFS “layer 0” — all starting sources.fresh === 0 already, return 0 — no work needed.layerSize = queue.length - head (the number of cells added to this wave). Process exactly that many cells; for each, check all 4 neighbors. If a neighbor is fresh, mark it rotten, decrement fresh, and enqueue it. After each full layer, increment minutes.fresh === 0 ? minutes - 1 : -1. The -1 is because the last layer that processes the final rotten cells still increments minutes once more after they infect nothing new.minutes after each layer — including the last one where no new cells are infected. That extra increment is why you return minutes - 1, not minutes. Many solvers get an off-by-one here. An alternative is to only increment when at least one neighbor was infected, but tracking that flag adds code.Space is also O(mn) for the queue in the worst case (all cells rotten at once). The grid is mutated in-place (marking 1 → 2) so no extra visited array is needed.
1function orangesRotting(grid: number[][]): number {2 const rows = grid.length;3 const cols = grid[0].length;4 const queue: [number, number][] = [];5 let fresh = 0;67 // Seed: enqueue ALL initially rotten cells at once (multi-source BFS)8▶ for (let r = 0; r < rows; r++) {9▶ for (let c = 0; c < cols; c++) {10▶ if (grid[r][c] === 2) queue.push([r, c]);11▶ else if (grid[r][c] === 1) fresh++;12▶ }13▶ }1415 if (fresh === 0) return 0; // nothing to rot1617 const dirs: [number, number][] = [[-1,0],[1,0],[0,-1],[0,1]];18 let minutes = 0;19 let head = 0; // pointer into queue (avoids O(n) shift)2021 while (head < queue.length) {22 const layerSize = queue.length - head; // all cells in current minute23 for (let i = 0; i < layerSize; i++) {24 const [r, c] = queue[head++];25 for (const [dr, dc] of dirs) {26 const nr = r + dr;27 const nc = c + dc;28 if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 1) {29 grid[nr][nc] = 2; // mark rotten in-place (visited)30 fresh--;31 queue.push([nr, nc]);32 }33 }34 }35 minutes++;36 }3738 return fresh === 0 ? minutes - 1 : -1;39}
function orangesRotting(grid: number[][]): number {
const rows = grid.length;
const cols = grid[0].length;
const queue: [number, number][] = [];
let fresh = 0;
// Seed: enqueue ALL initially rotten cells at once (multi-source BFS)
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] === 2) queue.push([r, c]);
else if (grid[r][c] === 1) fresh++;
}
}
if (fresh === 0) return 0; // nothing to rot
const dirs: [number, number][] = [[-1,0],[1,0],[0,-1],[0,1]];
let minutes = 0;
let head = 0; // pointer into queue (avoids O(n) shift)
while (head < queue.length) {
const layerSize = queue.length - head; // all cells in current minute
for (let i = 0; i < layerSize; i++) {
const [r, c] = queue[head++];
for (const [dr, dc] of dirs) {
const nr = r + dr;
const nc = c + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 1) {
grid[nr][nc] = 2; // mark rotten in-place (visited)
fresh--;
queue.push([nr, nc]);
}
}
}
minutes++;
}
return fresh === 0 ? minutes - 1 : -1;
}queue and counts fresh. Crucially, every rotten cell is added before any BFS step, so they all spread simultaneously.head is a pointer into the shared array used as a queue; advancing it is O(1) and avoids the O(n) cost of Array.shift(). layerSizecaptures how many cells belong to the current minute's wave.grid[nr][nc] = 2 acts as the visited marker), decrements fresh, and is appended to the queue for the next layer. After the inner loop, minutes++.minutes. Return minutes - 1 to correct for this. If fresh > 0 isolated cells remain, return -1.grid itself serves as the visited set, so no extra boolean matrix is needed.boolean[][] visited grid instead of overwriting grid[r][c] = 2. Same time and space.dirs from 4 to 8 entries — the BFS structure is unchanged.function orangesRotting(grid: number[][]): number {
const rows = grid.length;
const cols = grid[0].length;
const queue: [number, number][] = [];
let fresh = 0;
// Seed: enqueue ALL initially rotten cells at once (multi-source BFS)
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] === 2) queue.push([r, c]);
else if (grid[r][c] === 1) fresh++;
}
}
if (fresh === 0) return 0; // nothing to rot
const dirs: [number, number][] = [[-1,0],[1,0],[0,-1],[0,1]];
let minutes = 0;
let head = 0; // pointer into queue (avoids O(n) shift)
while (head < queue.length) {
const layerSize = queue.length - head; // all cells in current minute
for (let i = 0; i < layerSize; i++) {
const [r, c] = queue[head++];
for (const [dr, dc] of dirs) {
const nr = r + dr;
const nc = c + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 1) {
grid[nr][nc] = 2; // mark rotten in-place (visited)
fresh--;
queue.push([nr, nc]);
}
}
}
minutes++;
}
return fresh === 0 ? minutes - 1 : -1;
}grid itself serves as the visited set, so no extra boolean matrix is needed.Each minute, scan the whole grid, find every cell that is rotten this minute, and rot its fresh neighbors for next minute. Repeat until a full pass rots nothing.
function orangesRotting(grid: number[][]): number {
const rows = grid.length;
const cols = grid[0].length;
let minutes = 0;
for (;;) {
// Collect cells to rot this minute first, so freshly-rotted cells
// in THIS pass don't spread again until the next minute.
const toRot: [number, number][] = [];
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] !== 2) continue;
for (const [dr, dc] of [[-1,0],[1,0],[0,-1],[0,1]]) {
const nr = r + dr;
const nc = c + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 1) {
toRot.push([nr, nc]);
}
}
}
}
if (toRot.length === 0) break; // a stable minute: nothing more spreads
for (const [r, c] of toRot) grid[r][c] = 2;
minutes++;
}
// Any surviving fresh orange means it was unreachable.
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] === 1) return -1;
}
}
return minutes;
}O(m·n) times the number of minutes (up to m·n itself). Multi-source BFS visits each cell only once for O(m·n) total.| spread / infection from multiple starting points | multi-source BFS |
| "minimum time" on a grid with simultaneous sources | BFS layers = minutes |
| some cells may be unreachable → return -1 | check fresh count after BFS |
| "nearest X" from any of many cells | seed BFS queue with all X cells upfront |
const queue: [number, number][] = [];
let fresh = 0;
for (let r = 0; r < rows; r++)
for (let c = 0; c < cols; c++) {
if (grid[r][c] === 2) queue.push([r, c]);
else if (grid[r][c] === 1) fresh++;
}
if (fresh === 0) return 0;
let minutes = 0, head = 0;
while (head < queue.length) {
const layerSize = queue.length - head;
for (let i = 0; i < layerSize; i++) {
const [r, c] = queue[head++];
// check 4 neighbors; if fresh, mark rotten + enqueue + fresh--
}
minutes++;
}
return fresh === 0 ? minutes - 1 : -1;[[2,1,1],[1,1,0],[0,1,1]], what does the algorithm return?minutes - 1 rather than minutes?[[0,2]]. What does the algorithm return?[[2,1,1],[0,1,1],[1,0,1]]. What does the algorithm return?