Minutes until every orange rots
A grid is a list of equal-length strings of '0' (empty), '1' (fresh
orange) and '2' (rotten orange). Every minute, each fresh orange that shares an edge with a rotten
one becomes rotten. Return the number of minutes until no fresh orange remains, 0 if there is none
to begin with, or -1 if some fresh orange can never rot.
Examples
| Input | Output | Why |
|---|---|---|
grid = ["211", "110", "011"] | 4 | The rot spreads from the top-left corner and reaches the bottom-right orange in the fourth minute. |
grid = ["211", "011", "101"] | -1 | The bottom-left orange has no neighbour that can ever rot. |
grid = ["02"] | 0 | No fresh oranges: nothing to wait for. |
Constraints
- 1 ≤ rows, cols ≤ 300
- Every row has the same length
- Cells are '0', '1' or '2'
Hints
Hint 1
Each minute is one step of a breadth-first search. Where does the search start?
Hint 2
Put every rotten orange in the queue at the start, all at distance 0: a search from many sources at once.
Hint 3
Count the fresh oranges first. Each minute, the newly rotten ones are the next ring. Stop when the queue empties; if fresh ones remain, return −1.
Where people lose marks
- Starting a separate search from each rotten orange: correct, and quadratic.
- Counting the final ring, which rots nothing, as a minute: off by one.
- Returning the minutes without checking that every fresh orange was reached.
- Rotting oranges in place while still reading the same minute's grid, so the rot travels several cells in one minute.
Dry run on ["211","110","011"]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 6row 0R0F1F2row 1F0F1·2row 2·0F1F2minute0fresh6
1 rotten orange starts the search together; 6 fresh to go.
-
Step 2 / 6row 0R0R1F2row 1R0F1·2row 2·0F1F2minute1fresh4
Minute 1: 2 oranges next to the last ring rot.
-
Step 3 / 6row 0R0R1R2row 1R0R1·2row 2·0F1F2minute2fresh2
Minute 2: 2 oranges next to the last ring rot.
-
Step 4 / 6row 0R0R1R2row 1R0R1·2row 2·0R1F2minute3fresh1
Minute 3: 1 orange next to the last ring rots.
-
Step 5 / 6row 0R0R1R2row 1R0R1·2row 2·0R1R2minute4fresh0
Minute 4: 1 orange next to the last ring rots.
-
Step 6 / 6row 0R0R1R2row 1R0R1·2row 2·0R1R2answer4
No fresh oranges left after 4 minutes.
How to think about it
The time at which an orange rots is its distance to the nearest rotten orange at the start. A breadth-first search computes distances from one source; putting every initially rotten orange in the queue at distance 0 computes the distance to the nearest of them, as if a single invisible source were joined to all of them.
- Count the fresh oranges and enqueue every rotten one.
- Process the queue ring by ring. Each fresh neighbour rots, is counted off, and joins the next ring.
- The number of rings that rotted at least one orange is the answer, provided the fresh count reaches 0.
Running a separate search from each rotten orange and taking minimums gives the same answer in O((rows · cols)²) time. The multi-source start does it in one pass.
Complexity. Time O(rows · cols) · Space O(rows · cols)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
from collections import deque
def oranges_rotting(grid):
rows, cols = len(grid), len(grid[0])
state = [list(row) for row in grid]
q = deque((r, c) for r in range(rows) for c in range(cols) if state[r][c] == '2')
fresh = sum(row.count('1') for row in state)
minutes = 0
while q and fresh:
for _ in range(len(q)): # one ring: everything that rotted last minute
i, j = q.popleft()
for a, b in ((i + 1, j), (i - 1, j), (i, j + 1), (i, j - 1)):
if 0 <= a < rows and 0 <= b < cols and state[a][b] == '1':
state[a][b] = '2' # rots now: never queued twice
fresh -= 1
q.append((a, b))
minutes += 1
return -1 if fresh else minutes#include <string>
#include <utility>
#include <vector>
int solve(const std::vector<std::string>& grid) {
std::vector<std::string> state = grid;
const int rows = state.size(), cols = state[0].size();
const int dr[] = {1, -1, 0, 0}, dc[] = {0, 0, 1, -1};
std::vector<std::pair<int, int>> ring;
int fresh = 0;
for (int r = 0; r < rows; ++r)
for (int c = 0; c < cols; ++c) {
if (state[r][c] == '2') ring.push_back({r, c});
else if (state[r][c] == '1') ++fresh;
}
int minutes = 0;
while (!ring.empty() && fresh > 0) {
std::vector<std::pair<int, int>> next; // the ring that rots this minute
for (auto [i, j] : ring)
for (int k = 0; k < 4; ++k) {
const int a = i + dr[k], b = j + dc[k];
if (a >= 0 && a < rows && b >= 0 && b < cols && state[a][b] == '1') {
state[a][b] = '2';
--fresh;
next.push_back({a, b});
}
}
ring.swap(next);
++minutes;
}
return fresh > 0 ? -1 : minutes;
}class Solution {
public int orangesRotting(String[] grid) {
int rows = grid.length, cols = grid[0].length();
char[][] state = new char[rows][];
for (int r = 0; r < rows; r++) state[r] = grid[r].toCharArray();
int[] dr = {1, -1, 0, 0}, dc = {0, 0, 1, -1};
java.util.ArrayDeque<int[]> q = new java.util.ArrayDeque<>();
int fresh = 0;
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++) {
if (state[r][c] == '2') q.add(new int[]{r, c});
else if (state[r][c] == '1') fresh++;
}
int minutes = 0;
while (!q.isEmpty() && fresh > 0) {
for (int k = q.size(); k > 0; k--) { // one ring: last minute's rot
int[] cell = q.poll();
for (int d = 0; d < 4; d++) {
int a = cell[0] + dr[d], b = cell[1] + dc[d];
if (a >= 0 && a < rows && b >= 0 && b < cols && state[a][b] == '1') {
state[a][b] = '2';
fresh--;
q.add(new int[]{a, b});
}
}
}
minutes++;
}
return fresh > 0 ? -1 : minutes;
}
}function orangesRotting(grid) {
const rows = grid.length, cols = grid[0].length;
const state = grid.map(row => [...row]);
let q = [], fresh = 0;
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
if (state[r][c] === '2') q.push([r, c]);
else if (state[r][c] === '1') fresh++;
}
let minutes = 0;
while (q.length && fresh) {
const next = []; // the ring that rots this minute
for (const [i, j] of q) {
for (const [a, b] of [[i + 1, j], [i - 1, j], [i, j + 1], [i, j - 1]]) {
if (a >= 0 && a < rows && b >= 0 && b < cols && state[a][b] === '1') {
state[a][b] = '2';
fresh--;
next.push([a, b]);
}
}
}
q = next;
minutes++;
}
return fresh ? -1 : minutes;
}fn solve(grid: &[&str]) -> i64 {
let mut state: Vec<Vec<u8>> = grid.iter().map(|row| row.as_bytes().to_vec()).collect();
let (rows, cols) = (state.len(), state[0].len());
let mut ring = Vec::new();
let mut fresh = 0;
for r in 0..rows {
for c in 0..cols {
match state[r][c] {
b'2' => ring.push((r, c)),
b'1' => fresh += 1,
_ => {}
}
}
}
let mut minutes = 0;
while !ring.is_empty() && fresh > 0 {
let mut next = Vec::new(); // the ring that rots this minute
for &(i, j) in &ring {
let mut around = Vec::with_capacity(4);
if i > 0 { around.push((i - 1, j)); }
if i + 1 < rows { around.push((i + 1, j)); }
if j > 0 { around.push((i, j - 1)); }
if j + 1 < cols { around.push((i, j + 1)); }
for (a, b) in around {
if state[a][b] == b'1' {
state[a][b] = b'2';
fresh -= 1;
next.push((a, b));
}
}
}
ring = next;
minutes += 1;
}
if fresh > 0 { -1 } else { minutes }
}import qualified Data.Set as S
-- Spread from every rotten orange at once, one ring per minute.
solve :: [String] -> Int
solve grid = go 0 rotten (S.fromList fresh0)
where
cells ch = [(r, c) | (r, row) <- zip [0 :: Int ..] grid, (c, x) <- zip [0 :: Int ..] row, x == ch]
rotten = cells '2'
fresh0 = cells '1'
go t ring fresh
| S.null fresh = t
| null next = -1
| otherwise = go (t + 1) next (foldr S.delete fresh next)
where
next = S.toList (S.fromList [q | (r, c) <- ring, q <- [(r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)], q `S.member` fresh])
Variants to try
- Return the minute at which each orange rots.
- Walls block the spread (a fourth cell type). Nothing changes in the algorithm; check that.
- Distance to the nearest exit for every cell of a maze: the same multi-source search, started from the exits.
Next problems
Shortest path through a grid
Breadth-first search reaches every cell in the fewest moves because it explores in rings of equal distance. Why it beats depth-first search is the lesson.
Open the problem → Medium20 minDistance to the nearest zero
For every cell of a 0/1 grid, return the number of steps to the nearest 0. One breadth-first search that starts from all the zeros at once.
Open the problem → Hard30 minShortest word transformation
Change one letter at a time, using only dictionary words, from a start word to an end word. Each word is a node; breadth-first search finds the shortest ladder.
Open the problem →Write a solution and run it against the real test table.
Keep going
Shortest path through a grid
Breadth-first search reaches every cell in the fewest moves because it explores in rings of equal distance. Why it beats depth-first search is the lesson.
Open the problem → Medium20 minDistance to the nearest zero
For every cell of a 0/1 grid, return the number of steps to the nearest 0. One breadth-first search that starts from all the zeros at once.
Open the problem → Hard30 minShortest word transformation
Change one letter at a time, using only dictionary words, from a start word to an end word. Each word is a node; breadth-first search finds the shortest ladder.
Open the problem → GraphsThe pattern behind it
Grids, networks and dependencies are all graphs. Breadth-first search finds shortest paths, depth-first search and union-find find what is connected, Dijkstra handles weighted edges, and a queue of nodes with no remaining prerequisites gives an order.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.