994. Rotting Oranges
Problem Description
You have a grid of size m x n
where each cell contains one of three possible values:
0
: an empty cell1
: a fresh orange2
: a rotten orange
The rotting process works as follows: every minute, any fresh orange that is directly adjacent (up, down, left, or right) to a rotten orange will become rotten.
Your task is to find the minimum number of minutes needed for all fresh oranges to become rotten. If it's impossible for all fresh oranges to rot (for example, if a fresh orange is isolated and cannot be reached by any rotten orange), return -1
.
For example, if you have a grid where rotten oranges are at certain positions and fresh oranges are adjacent to them, the rotting will spread minute by minute. After the first minute, all fresh oranges directly next to the initial rotten oranges become rotten. After the second minute, the newly rotten oranges from minute 1 will cause their adjacent fresh oranges to rot, and so on.
The algorithm uses BFS (Breadth-First Search) to simulate this minute-by-minute spreading process. It starts by finding all initially rotten oranges and counting all fresh oranges. Then it processes the rotting in rounds, where each round represents one minute. In each round, all currently rotten oranges spread the rot to their adjacent fresh oranges simultaneously. The process continues until either all fresh oranges have rotted (return the number of minutes elapsed) or no more fresh oranges can be reached (return -1
).
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The grid can be viewed as a graph where each cell is a node, and edges connect to 4-directionally adjacent cells. The rotting process spreads from rotten oranges to fresh ones through these connections.
Is it a tree?
- No: The grid structure has multiple paths between cells and can contain cycles, so it's not a tree.
Is the problem related to directed acyclic graphs?
- No: The grid allows bidirectional movement between adjacent cells, and the problem isn't about directed relationships.
Is the problem related to shortest paths?
- Yes: We need to find the minimum time (shortest path in terms of steps) for the rot to spread from all rotten oranges to all fresh oranges.
Is the graph Weighted?
- No: Each step of spreading takes exactly 1 minute, so all edges have the same weight (unweighted graph).
BFS
- Yes: Since we have an unweighted shortest path problem, BFS is the appropriate algorithm.
Conclusion: The flowchart suggests using BFS (Breadth-First Search) for this problem. BFS is perfect here because:
- We need to simulate the simultaneous spreading from multiple sources (all initially rotten oranges)
- Each level of BFS represents one minute of time
- BFS guarantees we find the minimum time needed since it explores all cells at distance k before exploring cells at distance k+1
Intuition
The key insight is to think of the rotting process as waves spreading outward from multiple sources simultaneously. Imagine dropping multiple stones into a pond at the same time - the ripples expand outward from each stone, and where they meet, they've both reached that point at the same time.
In our problem, the rotten oranges are like those stones, and the rotting process spreads like ripples. Each "wave" or "ripple" represents one minute of time. All oranges at distance 1 from any rotten orange will rot after 1 minute, all oranges at distance 2 will rot after 2 minutes, and so on.
This naturally leads us to BFS because:
- BFS processes nodes level by level, which perfectly models the minute-by-minute spreading
- We can start with all initially rotten oranges in our queue (multiple starting points)
- Each level of BFS represents one unit of time
The algorithm maintains a counter cnt
for fresh oranges. As we process each level:
- We rot all reachable fresh oranges at the current distance
- We decrement
cnt
for each newly rotten orange - We track how many levels (minutes) we've processed
The beauty of this approach is that BFS guarantees we're always rotting oranges in the optimal order - closest ones first. When cnt
reaches 0, we know all fresh oranges have rotted, and the number of levels processed equals the minimum time needed.
If after BFS completes, cnt > 0
, it means some fresh oranges were unreachable (isolated), so we return -1
.
Learn more about Breadth-First Search patterns.
Solution Approach
The implementation follows a standard BFS pattern with some preprocessing:
Step 1: Initial Setup and Counting First, we traverse the entire grid once to:
- Find all initially rotten oranges (value
2
) and add their coordinates(i, j)
to a queueq
- Count all fresh oranges (value
1
) and store in variablecnt
- This preprocessing helps us know our starting points and when to stop
Step 2: BFS Initialization
- Initialize
ans = 0
to track the number of minutes elapsed - Define directions array
dirs = (-1, 0, 1, 0, -1)
for 4-directional movement- Using
pairwise(dirs)
gives us[(-1, 0), (0, 1), (1, 0), (0, -1)]
representing up, right, down, left
- Using
Step 3: Level-by-Level BFS Processing
The main BFS loop continues while q
is not empty AND cnt > 0
:
- Increment
ans
at the start of each level (representing one minute passing) - Process all oranges at the current level using
for _ in range(len(q))
:- For each rotten orange
(i, j)
, check all 4 adjacent cells - Calculate new coordinates:
x, y = i + a, j + b
- If the adjacent cell is within bounds and contains a fresh orange (
grid[x][y] == 1
):- Mark it as rotten:
grid[x][y] = 2
- Add it to queue for next level:
q.append((x, y))
- Decrement fresh count:
cnt -= 1
- Early termination: if
cnt == 0
, immediately returnans
- Mark it as rotten:
- For each rotten orange
Step 4: Final Result After BFS completes:
- If
cnt > 0
: Some fresh oranges couldn't be reached, return-1
- If
cnt == 0
: All fresh oranges have rotted, return0
(orans
if terminated early)
The time complexity is O(m Γ n)
where we visit each cell at most once. The space complexity is also O(m Γ n)
in the worst case when all cells contain rotten oranges initially.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let me walk through a small example to illustrate how the BFS solution works.
Consider this 3Γ3 grid:
2 1 1 1 1 0 0 1 1
Initial Setup (Step 1):
- Scan the grid to find rotten oranges and count fresh ones
- Found rotten orange at position (0,0)
- Fresh orange count
cnt = 6
- Queue
q = [(0,0)]
Minute 1 (First BFS Level):
- Process position (0,0), check its 4 neighbors:
- Up: out of bounds
- Right: (0,1) has fresh orange β rot it, add to queue
- Down: (1,0) has fresh orange β rot it, add to queue
- Left: out of bounds
- Grid after minute 1:
2 2 1 2 1 0 0 1 1
- Queue for next level:
[(0,1), (1,0)]
- Fresh count:
cnt = 4
- Time elapsed:
ans = 1
Minute 2 (Second BFS Level):
- Process (0,1):
- Right neighbor (0,2) has fresh orange β rot it
- Down neighbor (1,1) has fresh orange β rot it
- Process (1,0):
- Right neighbor (1,1) already being rotted this round
- Down neighbor (2,0) is empty, skip
- Grid after minute 2:
2 2 2 2 2 0 0 1 1
- Queue for next level:
[(0,2), (1,1)]
- Fresh count:
cnt = 2
- Time elapsed:
ans = 2
Minute 3 (Third BFS Level):
- Process (0,2):
- No fresh neighbors
- Process (1,1):
- Down neighbor (2,1) has fresh orange β rot it
- Grid after minute 3:
2 2 2 2 2 0 0 2 1
- Queue for next level:
[(2,1)]
- Fresh count:
cnt = 1
- Time elapsed:
ans = 3
Minute 4 (Fourth BFS Level):
- Process (2,1):
- Right neighbor (2,2) has fresh orange β rot it
- Grid after minute 4:
2 2 2 2 2 0 0 2 2
- Fresh count:
cnt = 0
β All oranges are rotten! - Return
ans = 4
The algorithm correctly determines that it takes 4 minutes for all fresh oranges to rot, with the rotting spreading outward level by level from the initial rotten orange at the top-left corner.
Solution Implementation
1class Solution:
2 def orangesRotting(self, grid: List[List[int]]) -> int:
3 # Get grid dimensions
4 rows, cols = len(grid), len(grid[0])
5
6 # Count fresh oranges and collect initial rotten oranges
7 fresh_count = 0
8 queue = deque()
9
10 # Scan the grid to find all rotten oranges (value 2) and count fresh oranges (value 1)
11 for row_idx in range(rows):
12 for col_idx in range(cols):
13 if grid[row_idx][col_idx] == 2:
14 # Add rotten orange position to queue
15 queue.append((row_idx, col_idx))
16 elif grid[row_idx][col_idx] == 1:
17 # Count fresh oranges
18 fresh_count += 1
19
20 # Time elapsed (in minutes)
21 minutes_elapsed = 0
22
23 # Direction vectors for 4-directional movement (up, right, down, left)
24 directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]
25
26 # BFS to rot adjacent fresh oranges
27 while queue and fresh_count > 0:
28 minutes_elapsed += 1
29
30 # Process all oranges that rot in the current minute
31 current_level_size = len(queue)
32 for _ in range(current_level_size):
33 curr_row, curr_col = queue.popleft()
34
35 # Check all 4 adjacent cells
36 for row_delta, col_delta in directions:
37 next_row = curr_row + row_delta
38 next_col = curr_col + col_delta
39
40 # Check if the adjacent cell is within bounds and contains a fresh orange
41 if (0 <= next_row < rows and
42 0 <= next_col < cols and
43 grid[next_row][next_col] == 1):
44
45 # Rot the fresh orange
46 grid[next_row][next_col] = 2
47 queue.append((next_row, next_col))
48 fresh_count -= 1
49
50 # Early termination if all oranges are rotten
51 if fresh_count == 0:
52 return minutes_elapsed
53
54 # Return -1 if there are still fresh oranges, otherwise return 0
55 return -1 if fresh_count > 0 else 0
56
1class Solution {
2 public int orangesRotting(int[][] grid) {
3 int rows = grid.length;
4 int cols = grid[0].length;
5
6 // Queue to store positions of rotten oranges for BFS
7 Deque<int[]> queue = new ArrayDeque<>();
8
9 // Count of fresh oranges
10 int freshCount = 0;
11
12 // Initial scan: find all rotten oranges and count fresh ones
13 for (int row = 0; row < rows; row++) {
14 for (int col = 0; col < cols; col++) {
15 if (grid[row][col] == 1) {
16 // Fresh orange found
17 freshCount++;
18 } else if (grid[row][col] == 2) {
19 // Rotten orange found, add to queue
20 queue.offer(new int[] {row, col});
21 }
22 }
23 }
24
25 // Direction vectors for 4-directional movement (up, right, down, left)
26 final int[] directions = {-1, 0, 1, 0, -1};
27
28 // BFS to rot adjacent fresh oranges level by level
29 for (int minutes = 1; !queue.isEmpty() && freshCount > 0; minutes++) {
30 // Process all oranges at current level
31 int levelSize = queue.size();
32
33 for (int i = 0; i < levelSize; i++) {
34 int[] currentPosition = queue.poll();
35 int currentRow = currentPosition[0];
36 int currentCol = currentPosition[1];
37
38 // Check all 4 adjacent cells
39 for (int dir = 0; dir < 4; dir++) {
40 int newRow = currentRow + directions[dir];
41 int newCol = currentCol + directions[dir + 1];
42
43 // Check if the new position is valid and contains a fresh orange
44 if (newRow >= 0 && newRow < rows &&
45 newCol >= 0 && newCol < cols &&
46 grid[newRow][newCol] == 1) {
47
48 // Rot the fresh orange
49 grid[newRow][newCol] = 2;
50 queue.offer(new int[] {newRow, newCol});
51 freshCount--;
52
53 // If all fresh oranges are rotten, return the time taken
54 if (freshCount == 0) {
55 return minutes;
56 }
57 }
58 }
59 }
60 }
61
62 // Return -1 if fresh oranges remain, 0 if no fresh oranges existed initially
63 return freshCount > 0 ? -1 : 0;
64 }
65}
66
1class Solution {
2public:
3 int orangesRotting(vector<vector<int>>& grid) {
4 int rows = grid.size();
5 int cols = grid[0].size();
6
7 // Queue to store positions of rotten oranges for BFS
8 queue<pair<int, int>> rottenQueue;
9
10 // Count of fresh oranges
11 int freshCount = 0;
12
13 // Initial scan: count fresh oranges and enqueue all rotten oranges
14 for (int row = 0; row < rows; ++row) {
15 for (int col = 0; col < cols; ++col) {
16 if (grid[row][col] == 1) {
17 // Fresh orange found
18 ++freshCount;
19 } else if (grid[row][col] == 2) {
20 // Rotten orange found, add to queue
21 rottenQueue.emplace(row, col);
22 }
23 }
24 }
25
26 // Direction vectors for 4-directional movement (up, right, down, left)
27 const int directions[5] = {-1, 0, 1, 0, -1};
28
29 // BFS to rot adjacent fresh oranges level by level
30 // Start from minute 1 since we're counting elapsed time
31 for (int minute = 1; !rottenQueue.empty() && freshCount > 0; ++minute) {
32 // Process all oranges that rot in the current minute
33 int currentLevelSize = rottenQueue.size();
34
35 for (int i = 0; i < currentLevelSize; ++i) {
36 // Get current rotten orange position
37 auto [currentRow, currentCol] = rottenQueue.front();
38 rottenQueue.pop();
39
40 // Check all 4 adjacent cells
41 for (int dir = 0; dir < 4; ++dir) {
42 int nextRow = currentRow + directions[dir];
43 int nextCol = currentCol + directions[dir + 1];
44
45 // Check if adjacent cell is valid and contains a fresh orange
46 if (nextRow >= 0 && nextRow < rows &&
47 nextCol >= 0 && nextCol < cols &&
48 grid[nextRow][nextCol] == 1) {
49
50 // Rot the fresh orange
51 grid[nextRow][nextCol] = 2;
52 rottenQueue.emplace(nextRow, nextCol);
53
54 // Decrease fresh orange count and check if all are rotten
55 if (--freshCount == 0) {
56 return minute;
57 }
58 }
59 }
60 }
61 }
62
63 // If there are still fresh oranges, return -1; otherwise return 0
64 return freshCount > 0 ? -1 : 0;
65 }
66};
67
1/**
2 * Calculates the minimum time for all fresh oranges to rot
3 * @param grid - 2D array where 0 = empty, 1 = fresh orange, 2 = rotten orange
4 * @returns Minimum minutes for all oranges to rot, or -1 if impossible
5 */
6function orangesRotting(grid: number[][]): number {
7 const rows: number = grid.length;
8 const cols: number = grid[0].length;
9
10 // Queue to store positions of initially rotten oranges
11 const queue: number[][] = [];
12 // Counter for fresh oranges
13 let freshOranges: number = 0;
14
15 // Initial scan: count fresh oranges and find all rotten oranges
16 for (let row: number = 0; row < rows; ++row) {
17 for (let col: number = 0; col < cols; ++col) {
18 if (grid[row][col] === 1) {
19 // Count fresh oranges
20 freshOranges++;
21 } else if (grid[row][col] === 2) {
22 // Add rotten orange position to queue
23 queue.push([row, col]);
24 }
25 }
26 }
27
28 // Direction vectors for 4-directional movement (up, right, down, left)
29 const directions: number[] = [-1, 0, 1, 0, -1];
30
31 // BFS to spread rot to adjacent fresh oranges
32 for (let minutes = 1; queue.length && freshOranges; ++minutes) {
33 // Temporary queue for next level of BFS
34 const nextLevel: number[][] = [];
35
36 // Process all oranges at current time level
37 for (const [currentRow, currentCol] of queue) {
38 // Check all 4 adjacent cells
39 for (let dir = 0; dir < 4; ++dir) {
40 const nextRow: number = currentRow + directions[dir];
41 const nextCol: number = currentCol + directions[dir + 1];
42
43 // Check if adjacent cell is valid and contains a fresh orange
44 if (nextRow >= 0 && nextRow < rows &&
45 nextCol >= 0 && nextCol < cols &&
46 grid[nextRow][nextCol] === 1) {
47
48 // Rot the fresh orange
49 grid[nextRow][nextCol] = 2;
50 // Add to next level queue
51 nextLevel.push([nextRow, nextCol]);
52
53 // Check if all fresh oranges are rotten
54 if (--freshOranges === 0) {
55 return minutes;
56 }
57 }
58 }
59 }
60
61 // Replace current queue with next level
62 queue.splice(0, queue.length, ...nextLevel);
63 }
64
65 // Return -1 if fresh oranges remain, 0 if no fresh oranges initially
66 return freshOranges > 0 ? -1 : 0;
67}
68
Time and Space Complexity
Time Complexity: O(m Γ n)
The algorithm performs a BFS traversal starting from all initially rotten oranges. In the worst case, every cell in the grid needs to be visited exactly once. The initial scan to find all rotten oranges and count fresh oranges takes O(m Γ n)
time. The BFS process visits each cell at most once, as each fresh orange becomes rotten exactly once and is added to the queue once. Therefore, the total time complexity is O(m Γ n)
.
Space Complexity: O(m Γ n)
The space complexity is determined by the queue used for BFS. In the worst-case scenario, all oranges could be rotten initially (all cells contain 2), which means the queue would need to store all m Γ n
positions at the start. Even in a more typical case where oranges rot progressively, the queue could potentially hold O(m Γ n)
elements at any given time (for example, when a wave of rotting spreads across a large portion of the grid simultaneously). Therefore, the space complexity is O(m Γ n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Minute Counting Logic
A common mistake is incrementing minutes_elapsed
at the wrong time or returning the wrong value when all oranges are already rotten initially.
Pitfall Example:
# WRONG: This increments time even when no fresh oranges exist while queue: minutes_elapsed += 1 # ... BFS logic return minutes_elapsed
Why it's wrong: If the grid has only rotten oranges (no fresh oranges), this would return a non-zero value when it should return 0.
Correct Approach:
# Only process BFS if there are fresh oranges to rot while queue and fresh_count > 0: minutes_elapsed += 1 # ... BFS logic # Return 0 if no fresh oranges existed initially return -1 if fresh_count > 0 else 0
2. Not Processing Level-by-Level in BFS
Pitfall Example:
# WRONG: Processing oranges one by one instead of level by level while queue and fresh_count > 0: curr_row, curr_col = queue.popleft() for row_delta, col_delta in directions: # ... rot adjacent oranges minutes_elapsed += 1 # Wrong! This counts each orange as a minute
Why it's wrong: Each minute, ALL currently rotten oranges should spread rot simultaneously. Processing them one-by-one would count each orange as a separate minute.
Correct Approach:
while queue and fresh_count > 0:
minutes_elapsed += 1
current_level_size = len(queue) # Capture current level size
for _ in range(current_level_size): # Process entire level
curr_row, curr_col = queue.popleft()
# ... rot adjacent oranges
3. Forgetting Edge Cases
Common scenarios to handle:
- Empty grid or grid with all empty cells (should return 0)
- Grid with only fresh oranges and no rotten ones (should return -1)
- Grid with only rotten oranges (should return 0)
- Single cell grids
Solution: Always check fresh_count
before and after BFS:
# After initial scan if fresh_count == 0: return 0 # No fresh oranges to rot # After BFS return -1 if fresh_count > 0 else minutes_elapsed
4. Modifying Grid Without Checking Bounds First
Pitfall Example:
# WRONG: Accessing grid before bounds check for row_delta, col_delta in directions: next_row = curr_row + row_delta next_col = curr_col + col_delta if grid[next_row][next_col] == 1: # IndexError possible! if 0 <= next_row < rows and 0 <= next_col < cols: # ...
Correct Approach: Always check bounds before accessing grid:
if (0 <= next_row < rows and 0 <= next_col < cols and grid[next_row][next_col] == 1): # Safe to access and modify
How many ways can you arrange the three letters A, B and C?
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!