Problem overview
The function orangesRotting receives a grid as a vector<vector<int>>. Each cell is 0 (empty), 1 (a fresh orange) or 2 (a rotten orange). Every minute, each fresh orange that is directly next to a rotten one, up, down, left or right, becomes rotten. The function returns how many minutes pass until no fresh orange is left, or -1 if some orange can never rot.
For the grid {{2, 1, 1}, {1, 1, 0}, {0, 1, 1}} the answer is 4. In minute 1 the two neighbours of the corner orange rot, and in each later minute the neighbours of the newly rotten oranges rot. For {{2, 1, 1}, {0, 1, 1}, {1, 0, 1}} the answer is -1: the orange at the bottom left has an empty cell above and to its right, and rot does not spread diagonally. For {{0, 2}} the answer is 0, because nothing is fresh at the start.
Approach: spread the rot level by level
The simple simulation scans the whole grid every minute and rots the neighbours of each rotten orange. A long winding line of oranges makes that slow: the rot creeps one cell per minute, so there can be about m * n minutes, each costing a scan of m * n cells. The statement asks for O(m * n).
The fix is a breadth-first search (BFS) that starts from all the rotten oranges at once. The oranges that are rotten at minute 0 form the first level. The fresh oranges they rot in minute 1 form the second level, the ones those rot in minute 2 form the third, and so on. The number of minutes is the number of levels that rot something. A queue handles this: oranges leave in the order they rotted, so one minute's oranges come out before the ones they rot.
A counter fresh tracks the oranges that are still fresh. When it reaches 0, the search stops. If the queue empties while fresh is still above 0, no chain of oranges links the rest to a rotten one, so they never rot and the answer is -1.
For the first example, the grid after the search is all rotten except the empty cells:
2 2 2
2 2 0
0 2 2
For the second example the search stops with the bottom-left orange still fresh:
2 2 2
0 2 2
1 0 2
Step by step
- Store the grid's size in
rows(grid.size()) andcols(grid[0].size()). Every row has the same length, so the first row gives the number of columns. - Create the queue
qofpair<int, int>values, (row, column) pairs. It holds the rotten oranges that are about to spread the rot. It is namedqbecausequeueis the name of the type. - Create
fresh, anintstarting at 0, to count the fresh oranges. - Scan the whole grid with nested loops over
randc. One pass finds the oranges that start rotten and counts the fresh ones. - Push every cell equal to 2 onto
qwithq.push({r, c}). All the starting rotten oranges spread the rot in the first minute, so they all go in before the search begins. - Count every cell equal to 1 with
fresh++. Empty cells are neither. - List the four moves in
directions, avector<pair<int, int>>of (row change, column change) pairs. Each changes only the row or only the column, so diagonal oranges are not neighbours. - Create
minutes, starting at 0. If nothing is fresh at the start, 0 is the answer. - Loop while
!q.empty() && fresh > 0. Each turn is one minute. Withoutfresh > 0the loop would count an extra minute after the last orange rots, while the last rotten oranges look for fresh neighbours that no longer exist. - Add 1 to
minutesand saveq.size()inlevelSize. These are the oranges that spread the rot in this minute. Oranges that rot during the minute join the same queue but only spread in the next one, so the minute handles exactlylevelSizeoranges. - Loop
levelSizetimes. One pass of this loop handles one BFS level. - Take the front orange with
auto [row, col] = q.front()and remove it withq.pop().popreturns nothing, so the front is read first. - Try each direction and compute
nextRowandnextCol. - Check the neighbour: it must be inside the grid and hold a fresh orange. The bounds come first in the condition, so the grid is read only for cells that exist.
- Rot it: set it to 2 right away, subtract 1 from
freshand push it ontoq. Marking it at once means no other orange rots it again or queues it a second time. - After the loop, return -1 if
freshis still above 0, andminutesotherwise.
The solution in C++
class Solution {
public:
int orangesRotting(vector<vector<int>> &grid) {
int rows = grid.size();
int cols = grid[0].size();
queue<pair<int, int>> q;
int fresh = 0;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (grid[r][c] == 2) {
q.push({r, c});
}
if (grid[r][c] == 1) {
fresh++;
}
}
}
vector<pair<int, int>> directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int minutes = 0;
while (!q.empty() && fresh > 0) {
minutes++;
int levelSize = q.size();
for (int i = 0; i < levelSize; i++) {
auto [row, col] = q.front();
q.pop();
for (auto [dr, dc] : directions) {
int nextRow = row + dr;
int nextCol = col + dc;
if (nextRow >= 0 && nextRow < rows && nextCol >= 0 && nextCol < cols && grid[nextRow][nextCol] == 1) {
grid[nextRow][nextCol] = 2;
fresh--;
q.push({nextRow, nextCol});
}
}
}
}
if (fresh > 0) {
return -1;
}
return minutes;
}
};
The C++ details worth a closer look:
- Structured bindings.
auto [row, col] = q.front()names the two members of the pair, a C++17 feature. It copies the pair, which matters becauseq.pop()destroys the queue's element right after. A reference taken fromfront()would dangle after the pop. - Brace initialization.
q.push({r, c})builds thepair<int, int>in place. The same braces filldirectionswith four pairs. &grid. The grid is passed by reference, soorangesRottingchanges the caller's grid: the oranges that rotted are 2 afterwards. The result does not need the original, but a caller that does should pass a copy.int levelSize = q.size(). The size is saved before the inner loop, becauseq.size()changes as oranges are pushed during the minute.&&short-circuits. WhennextRowis -1, the rest of the condition is not evaluated, sogrid[-1]is never read. Reading outside a vector is undefined behaviour in C++, not an exception.
Complexity
Let the grid have m rows and n columns.
Time: O(m * n). The first scan visits every cell. In the search, each cell joins the queue at most once, because it is marked rotten when it joins, and each cell looks at 4 neighbours.
Space: O(m * n). In the worst case, such as a grid of all rotten oranges, the queue holds every cell.
Edge cases and common mistakes
- No fresh oranges.
{{0, 2}},{{0}}and{{2, 2}, {2, 2}}all return 0,. - No rotten oranges.
{{1}}returns -1: the queue is empty, so the loop never runs, andfreshis still 1. - Fresh oranges cut off by empty cells.
{{2, 0, 1}}returns -1, because the empty cell between them stops the spread. - An off-by-one in the minutes.
{{2, 1, 1, 1, 1}}returns 4: four fresh oranges in a row, one per minute. Without thefresh > 0condition the loop would run one more turn and count 5. - Marking the orange when it leaves the queue. It should be marked when it joins. Otherwise several neighbours can queue the same orange, and
freshis reduced more than once for it. - Diagonal spreading. The four moves are the only ones. Adding diagonals would let the bottom-left orange of the second example rot through the orange at its upper right, which is not what the rules say.
- Not using the level size. Popping one orange per minute instead of a whole level counts a minute per orange.
Testing
{{2, 1, 1}, {1, 1, 0}, {0, 1, 1}}gives4.{{2, 1, 1}, {0, 1, 1}, {1, 0, 1}}gives-1.{{0, 2}}gives0.{{1}}gives-1.{{2, 1, 1, 1, 1}}gives4.{{2, 0, 1}}gives-1.
