Introduction to the Rotting Oranges Problem
The Rotting Oranges problem is a classic graph traversal challenge frequently encountered in coding interviews and competitive programming. It models a real-world scenario where rot spreads through a grid of oranges, and your task is to determine how long it takes for all fresh oranges to rot โ or whether it's even possible.
At its core, this problem tests your understanding of Breadth-First Search (BFS), multi-source traversal, and matrix manipulation. Mastering it builds a strong foundation for solving similar problems involving flood fill, shortest paths in grids, and cellular automata simulations.
Problem Statement
You are given an m x n grid where each cell can have one of three values:
0โ an empty cell1โ a fresh orange2โ a rotten orange
Every minute, any fresh orange that is 4-directionally adjacent to a rotten orange becomes rotten. You must return the minimum number of minutes that must elapse until no cell has a fresh orange. If this is impossible, return -1.
Why This Problem Matters
The Rotting Oranges problem is more than an interview exercise. It represents a class of problems where state changes propagate through a system over discrete time steps. Understanding it prepares you for:
- Multi-source BFS: Unlike standard BFS that starts from one node, here multiple rotten oranges start rotting simultaneously.
- Level-order traversal: Each minute corresponds to one BFS level, making it a natural fit for tracking time.
- Termination conditions: You must detect when all reachable fresh oranges have rotted and handle unreachable cases gracefully.
These concepts appear in real-world applications such as wildfire spread modeling, virus propagation simulations, network broadcast protocols, and image processing algorithms.
Approach: Multi-Source BFS
The optimal strategy uses BFS because it explores nodes level by level, and each level corresponds to one minute of rotting. The key insight is that all initially rotten oranges act as simultaneous starting points.
Step-by-Step Algorithm
- Traverse the grid to find all rotten oranges and count all fresh oranges.
- Enqueue all rotten orange positions into a queue โ these are the BFS sources.
- While the queue is not empty and fresh oranges remain, process the current level.
- For each rotten orange, rot its 4-directional fresh neighbors and enqueue them.
- Increment the minute counter after each BFS level completes.
- If fresh oranges remain after BFS finishes, return
-1; otherwise return the elapsed minutes.
Complete Python Implementation
Below is a clean, well-commented implementation using Python's collections.deque for efficient queue operations.
from collections import deque
from typing import List
def orangesRotting(grid: List[List[int]]) -> int:
if not grid or not grid[0]:
return -1
rows, cols = len(grid), len(grid[0])
queue = deque()
fresh_count = 0
# Step 1: Find all rotten oranges and count fresh ones
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2:
queue.append((r, c))
elif grid[r][c] == 1:
fresh_count += 1
# Edge case: no fresh oranges to rot
if fresh_count == 0:
return 0
# Step 2: BFS with level tracking
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
minutes = 0
while queue and fresh_count > 0:
# Process all oranges at the current minute level
level_size = len(queue)
for _ in range(level_size):
r, c = queue.popleft()
for dr, dc in directions:
nr, nc = r + dr, c + dc
if (0 <= nr < rows and 0 <= nc < cols
and grid[nr][nc] == 1):
grid[nr][nc] = 2
fresh_count -= 1
queue.append((nr, nc))
minutes += 1
# Step 3: Check if any fresh oranges remain unreachable
return minutes if fresh_count == 0 else -1
Testing the Solution
Let's verify the implementation with a few test cases to ensure correctness.
if __name__ == "__main__":
# Test case 1: Normal case
grid1 = [[2,1,1],[1,1,0],[0,1,1]]
print(orangesRotting(grid1)) # Expected: 4
# Test case 2: Impossible โ isolated fresh orange
grid2 = [[2,1,1],[0,1,1],[1,0,1]]
print(orangesRotting(grid2)) # Expected: -1
# Test case 3: No fresh oranges
grid3 = [[0,2]]
print(orangesRotting(grid3)) # Expected: 0
# Test case 4: All fresh, no rotten
grid4 = [[1,1],[1,1]]
print(orangesRotting(grid4)) # Expected: -1
# Test case 5: Single rotten orange
grid5 = [[2,1,1,1],[1,1,1,1],[1,1,1,1]]
print(orangesRotting(grid5)) # Expected: 6
Complexity Analysis
Understanding the time and space complexity helps you evaluate whether this solution scales to larger inputs.
- Time Complexity: O(m ร n) โ each cell is visited at most once during BFS, and the initial scan also takes O(m ร n).
- Space Complexity: O(m ร n) โ in the worst case, the queue holds all cells simultaneously (for example, when the entire grid is rotten at the start).
This is optimal because you must examine every cell at least once to determine the answer.
Best Practices and Common Pitfalls
1. Use Deque, Not Lists
Always use collections.deque for BFS queues. Popping from the front of a Python list is O(n), while deque.popleft() is O(1). This difference becomes significant on large grids.
2. Track Fresh Count Explicitly
Instead of scanning the grid again at the end to check for remaining fresh oranges, maintain a fresh_count variable. This avoids an extra O(m ร n) pass and makes the termination logic clearer.
3. Handle Edge Cases Early
Check for empty grids and grids with no fresh oranges before starting BFS. Returning early simplifies the main loop and prevents unnecessary work.
4. Mutate In-Place or Copy?
The implementation above mutates the grid in-place. If the caller needs the original grid preserved, create a deep copy first:
import copy
def orangesRottingPreserve(grid: List[List[int]]) -> int:
grid_copy = copy.deepcopy(grid)
# Run BFS on grid_copy instead
return orangesRotting(grid_copy)
5. Avoid Re-processing Rotten Cells
By checking grid[nr][nc] == 1 before enqueuing, you ensure each fresh orange is added to the queue exactly once. Marking it rotten immediately upon enqueueing โ rather than upon dequeuing โ prevents duplicate entries.
Variations and Extensions
Once you understand the core solution, try these variations to deepen your mastery:
- 8-directional rotting: Allow diagonals in addition to the four cardinal directions.
- Variable rotting speed: Different rotten oranges spread at different rates, requiring a priority queue.
- Obstacles: Add walls (value
3) that block the rot from spreading. - Return the grid state: Instead of returning minutes, return the final grid configuration after rotting completes.
Conclusion
The Rotting Oranges problem elegantly combines BFS traversal with level-order timing to model a spreading process. By enqueuing all rotten oranges as simultaneous sources, tracking fresh oranges explicitly, and processing the queue level by level, you arrive at a clean O(m ร n) solution. The patterns you learn here โ multi-source BFS, in-place grid mutation, and careful termination handling โ transfer directly to dozens of related problems in graph theory and grid-based simulation. Practice the variations, internalize the edge cases, and you'll be well equipped to tackle any spreading-process problem that comes your way.