994. Rotting Oranges¶
Edited by Charles
Medium · Python · Graph
Problem
Read the original problem on LeetCode.
The official page contains the problem statement, examples, and constraints.
Explanation, examples & other approaches
Core idea
You are given an m × n grid where:
Every minute, any fresh orange that is 4-directionally adjacent to a rotten orange becomes rotten.
Original study notes follow, including intermediate snippets and earlier attempts.
Problem¶
You are given an m × n grid where:
Every minute, any fresh orange that is 4-directionally adjacent to a rotten orange becomes rotten.
The four directions are:
Return the minimum number of minutes until no fresh orange remains.
If some fresh orange can never become rotten, return:
This problem is a classic:
The key observation is:
All initially rotten oranges start spreading at the same time.
Therefore, instead of starting BFS from only one rotten orange, we put all initially rotten oranges into the queue.
Suppose:
There are two rotten oranges:
Both of them start infecting nearby oranges at the same time.
Therefore, we should initialize:
and add every rotten orange:
Now the initial queue represents:
A rotten orange can infect its four neighbors:
So we define:
For the current orange:
and one direction:
the next position is:
Before accessing:
we must make sure the position is inside the grid:
and:
Therefore:
Notice that the lower bound must be:
not:
because Python uses 0-based indexing.
The spreading process happens level by level.
For example:
After one BFS level:
After another BFS level:
So:
This is exactly the structure of Level-Order BFS.
This is the most important part for counting time.
We write:
At the beginning of each while iteration:
contains all rotten oranges belonging to the current BFS level.
Suppose:
Then:
so:
processes exactly:
These three oranges spread simultaneously during the current minute.
During the loop, we may do:
For example:
While processing A, we may add D:
Then processing B may add E:
But the loop was created using the original:
So only three nodes are processed during this level:
The newly added:
remain in the queue for the next while iteration.
Therefore:
current queue level
↓
spread to neighbors
↓
new rotten oranges enter queue
↓
next BFS level
↓
next minute
This is how BFS naturally simulates time.
When we find a fresh orange:
we immediately change it to:
and then add it to the queue:
So:
This is important because changing:
also acts as a visited marker.
Normally BFS may require:
to prevent the same node from being added multiple times.
But here, when a fresh orange is discovered, we immediately do:
Therefore, if another rotten orange later reaches the same position, this condition:
will be false.
So the orange will not be added again.
Therefore:
serves two purposes:
No extra visited array is necessary.
This is a subtle but important part of this implementation.
We initialize:
instead of:
because the initial queue contains oranges that are already rotten at minute 0.
The first BFS level does not represent one minute of infection before it begins.
It represents processing the initial state.
For example:
Initially:
Our BFS processes the initial rotten orange first.
If:
then after processing the initial level:
gives:
Now the first newly rotten orange is in the queue.
After processing that level:
Then after processing the next level:
So the time correctly corresponds to the number of minutes needed.
Consider the simplest case:
There are no fresh oranges.
The correct answer is:
But if we initialize:
then the initial rotten orange is still in the queue.
The BFS processes it and executes:
giving:
which is incorrect.
By starting from:
we get:
which correctly represents:
There is another edge case.
Suppose:
There are:
So:
The BFS never runs.
Therefore:
remains unchanged.
But the correct answer should be:
because there are no fresh oranges that need to rot.
Therefore:
converts:
for this edge case.
After BFS finishes, some fresh oranges may still remain.
For example:
The bottom-left fresh orange is isolated.
It can never be reached by any rotten orange.
Therefore, after BFS, we scan the grid again:
If any:
still exists, return:
Otherwise, all fresh oranges were successfully infected.
Step 1¶
Create the queue and find all initial rotten oranges:
This creates a Multi-Source BFS.
Step 2¶
Process the queue level by level:
Each level represents one stage of the spreading process.
Step 3¶
For every rotten orange, check its four neighbors:
If the neighbor is a fresh orange:
mark it rotten:
and add it to the queue:
Step 4¶
After finishing one BFS level:
Step 5¶
After BFS, check whether any fresh orange remains:
Otherwise:
Let:
Each cell can enter the queue at most once.
Therefore, BFS takes:
The initial and final scans also take:
So the total time complexity is:
Space Complexity¶
In the worst case, the queue may contain:
cells.
Therefore:
This problem is a classic:
Multi-Source BFS¶
All initially rotten oranges are BFS starting points:
One BFS Level = One Stage of Time¶
Use:
to process exactly the nodes belonging to the current level.
Newly rotten oranges are added to the queue but processed in the next level.
No Separate visited¶
When a fresh orange becomes rotten:
it is already marked as visited.
time = -1¶
The initial rotten oranges already exist at:
so the initial BFS level should not count as an additional minute.
Therefore:
Check Remaining Fresh Oranges¶
After BFS:
means some fresh orange is unreachable.
The core pattern to remember is:
Put ALL sources into queue
↓
Process BFS level
↓
Spread to valid neighbors
↓
Mark neighbors visited immediately
↓
time += 1
↓
Process next level
This is the standard pattern for problems where:
Study record
| Field | Value |
|---|---|
| Attempts | 1 |
| Mastery | ❚ |
| The Latest Attempt | September 21, 2026 |
Python solution¶
from collections import deque
class Solution:
def orangesRotting(self, grid: list[list[int]]) -> int:
m, n = len(grid), len(grid[0])
dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)]
queue = deque()
time = -1
# Add all initially rotten oranges
for i in range(m):
for j in range(n):
if grid[i][j] == 2:
queue.append((i, j))
# Multi-Source + Level-Order BFS
while queue:
for _ in range(len(queue)):
curNode = queue.popleft()
for move in dirs:
nextNode = (
curNode[0] + move[0],
curNode[1] + move[1]
)
if (
0 <= nextNode[0] < m
and 0 <= nextNode[1] < n
and grid[nextNode[0]][nextNode[1]] == 1
):
grid[nextNode[0]][nextNode[1]] = 2
queue.append(nextNode)
time += 1
# Check whether any fresh orange is unreachable
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
return -1
return max(time, 0)