994. Rotting Oranges
Problem Description
This problem presents a scenario where we have a grid that represents a box of oranges. The oranges can either be fresh (represented by 1
), rotten (2
), or the cell can be empty (0
). The challenge is to find out how long it will take for all fresh oranges to become rotten given that each minute, every fresh orange that is adjacent (up, down, left, or right) to a rotten orange will become rotten itself. If it's impossible for all oranges to become rotten (e.g., if some oranges are unreachable by the rotten ones), we are to return -1
.
The problem expects us to determine the minimum number of minutes that must pass until there are no fresh oranges left or to assert that such a state is impossible to achieve.
Flowchart Walkthrough
For leetcode 994. Rotting Oranges, we can use the Flowchart to determine the appropriate algorithm. Here's a detailed walk-through based on the provided flowchart:
Is it a graph?
- Yes: The grid in the problem can be interpreted as a graph, where each cell is a node. Adjacent cells are connected by edges.
Is it a tree?
- No: The graph might contain cycles (oranges can rot back and forth in adjacencies), so it doesn’t strictly form a hierarchical tree structure.
Is the problem related to directed acyclic graphs (DAGs)?
- No: Direction does not play a role in the spreading of decay, and cyclic dependencies are not a concern in this problem.
Is the problem related to shortest paths?
- Yes: The core of the problem is to determine the minimum time (shortest path in terms of time steps) required for all fresh oranges to become rotten, if possible.
Is the graph weighted?
- No: Each movement or time step from one orange to an adjacent one takes the same amount of time; hence, the graph is unweighted.
Conclusion: Based on the flowchart, for an unweighted shortest path problem, BFS (Breadth-First Search) is the most suitable algorithm since it explores each level or layer (each minute in this context) uniformly, hence determining the minimum time required efficiently.
Intuition
The intuition behind the solution is to use a Breadth-First Search (BFS) approach, which is well-suited for problems involving levels or minutes passing by as in this case. We can think of each minute as a level in a BFS traversal.
The process begins by scanning the grid to do two things:
- Identify and enqueue all the initially rotten oranges (cells with a value
2
), as these will be the starting points for the spread of rot, and - Count the amount of fresh oranges (cells with a value
1
) because we need to keep track of when no fresh oranges are left.
The solution proceeds in rounds, where each round represents one minute. In each round, the solution:
- Increments the time counter once for all rotten oranges that will affect fresh ones in that minute.
- Dequeues a location of a rotten orange and checks its 4-directional neighbors.
- If a neighbor is a fresh orange, it becomes rotten. We decrement the count of fresh oranges and enqueue the new rotten orange's location for the next round of processing.
If there are no more fresh oranges (cnt = 0
), the BFS is complete, and the time counter (ans
) reflects the minimum number of minutes elapsed. If some fresh oranges were never reached (and therefore cnt
is not zero after the BFS), it's impossible to rot all oranges, and the function returns -1
.
Learn more about Breadth-First Search patterns.
Solution Approach
The implementation of the solution starts by initializing some basic variables:
m
,n
to store the dimensions of the grid.q
, a double-ended queue (deque
), which is used to perform efficient pop and append operations from both ends, crucial for BFS.cnt
to keep a count of the fresh oranges.
Next, we iterate through the grid to fill our queue q
with the initial positions of the rotten oranges and count the number of fresh oranges using cnt
. This setup phase is necessary to kickstart the BFS process.
Now, we define a variable ans
to keep track of the time taken in minutes. We execute a loop that continues as long as there are fresh oranges (cnt
) and there are rotten oranges in the queue to process (q
). Each iteration of the loop represents a passing minute where potential rotting of adjacent oranges occurs.
Inside the loop, we increment ans
for each round of minute and process all rotten oranges in the queue for that minute:
- Pop each position of a rotten orange from the front of the queue.
- Look at each of the four adjacent cells using the provided direction vectors
[[0, 1], [0, -1], [1, 0], [-1, 0]]
. - For each adjacent cell:
- Calculate the new coordinates
x
andy
based on the current positioni
,j
and the direction vectora
,b
. - Check if the adjacent cell is within the grid bounds and if it contains a fresh orange (
grid[x][y] == 1
). - If the adjacent cell is a fresh orange, decrement
cnt
, set that cell to2
(rotten), and append its position to the queueq
for processing in the next minute.
- Calculate the new coordinates
Once the BFS loop ends, we check whether there are any fresh oranges left untouched (cnt
> 0). If there are, we return -1
, indicating that it's impossible to rot all oranges. If cnt
is 0
, meaning all fresh oranges have been turned rotten, we return ans
, the total minutes elapsed.
The use of BFS ensures that we process all oranges that could potentially become rotten in a particular minute before moving on to the next minute. This closely emulates the passage of time and the spread of rot from orange to orange. The deque
data structure allows us to efficiently enqueue new rotten oranges for subsequent processing while still keeping track of the current round of oranges being processed.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a 3x3 grid of oranges to illustrate the solution approach:
2 1 0 1 1 0 0 0 1
In this example:
2
represents a rotten orange.1
represents a fresh orange.0
represents an empty cell.
First, we initialize our variables:
m
is 3 (number of rows).n
is 3 (number of columns).q
is an empty queue that will contain the positions of rotten oranges.cnt
is a count of fresh oranges, which we initialize to 0 and will increment as we find fresh oranges.ans
is initialized to -1, so if we do not find any fresh oranges, we will return -1.
We scan the grid and find that there is one rotten orange at position (0,0), and there are three fresh oranges. We add the position of the rotten orange to q
and increment cnt
as we count fresh oranges.
Now the queue q
and cnt
are as follows:
q
contains [(0, 0)]cnt
is 3 (as there are 3 fresh oranges)
Next, we set ans
to 0 as we prepare to simulate time passing.
Now, we begin our BFS loop. Since q
is not empty and cnt
is not zero, we proceed:
- As we enter the loop, we increment
ans
to represent the first minute passing. - We dequeue (0, 0) from
q
and examine its neighbors (0, 1), (1, 0), as only these two are within the bounds of the grid and adjacent to it. - Neighbor (0, 1) is a fresh orange, so we set it to rotten (
grid[0][1] = 2
), decrementcnt
to 2, and enqueue (0, 1) for the next round. - Neighbor (1, 0) is fresh as well, we perform the same operation: set it to rotten, decrement
cnt
to 1, and enqueue (1, 0).
The grid now looks like this:
2 2 0 2 1 0 0 0 1
And q
has [(0, 1), (1, 0)] with ans
being 1.
We enter the second minute:
- Increment
ans
to 2. - We have two positions in
q
to process: (0, 1) and (1, 0). - From (0, 1), we look at its neighbors, but none are fresh oranges.
- From (1, 0), we look at neighbor (1, 1) which is fresh and make it rotten, decrement
cnt
to 0, and enqueue (1, 1).
Now the grid and variables are:
2 2 0 2 2 0 0 0 1
q
contains [(1, 1)]cnt
is 0 (no more fresh oranges)ans
is 2.
Now we have processed all fresh oranges, so we exit the BFS loop and return ans
, which is 2. This is the minimum number of minutes until there are no fresh oranges left. The last orange at (2, 2) could never become rotten as it's not adjacent to any rotten or potentially rotting oranges, so it is not counted.
Solution Implementation
1from collections import deque
2
3class Solution:
4 def orangesRotting(self, grid: List[List[int]]) -> int:
5 # Get the number of rows and columns of the grid.
6 rows, cols = len(grid), len(grid[0])
7
8 # Initialize a queue for BFS and a counter for fresh oranges.
9 queue = deque()
10 fresh_count = 0
11
12 # Go through each cell in the grid.
13 for i in range(rows):
14 for j in range(cols):
15 # If we find a rotten orange, add its position to the queue.
16 if grid[i][j] == 2:
17 queue.append((i, j))
18 # If it's a fresh orange, increment the fresh_count.
19 elif grid[i][j] == 1:
20 fresh_count += 1
21
22 # Track the number of minutes passed.
23 minutes_passed = 0
24
25 # Perform BFS until the queue is empty or there are no fresh oranges left.
26 while queue and fresh_count > 0:
27 # Increment minutes each time we start a new round of BFS.
28 minutes_passed += 1
29
30 # Loop over all the rotten oranges at the current minute.
31 for _ in range(len(queue)):
32 i, j = queue.popleft()
33
34 # Check the adjacent cells in all four directions.
35 for delta_row, delta_col in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
36 x, y = i + delta_row, j + delta_col
37
38 # If the adjacent cell has a fresh orange, rot it.
39 if 0 <= x < rows and 0 <= y < cols and grid[x][y] == 1:
40 fresh_count -= 1
41 grid[x][y] = 2
42 queue.append((x, y))
43
44 # If there are no fresh oranges left, return the minutes passed.
45 # Otherwise, return -1 because some oranges will never rot.
46 return minutes_passed if fresh_count == 0 else -1
47
1class Solution {
2 public int orangesRotting(int[][] grid) {
3 int rows = grid.length, cols = grid[0].length;
4 int freshOranges = 0; // Counter for fresh oranges
5 Deque<int[]> queue = new LinkedList<>(); // Queue for rotten oranges' positions
6
7 // Preprocess the grid, enqueue all rotten oranges and count the fresh ones
8 for (int i = 0; i < rows; ++i) {
9 for (int j = 0; j < cols; ++j) {
10 if (grid[i][j] == 2) { // If orange is rotten
11 queue.offer(new int[] {i, j});
12 } else if (grid[i][j] == 1) { // If orange is fresh
13 freshOranges++;
14 }
15 }
16 }
17
18 int minutesElapsed = 0; // Time counter for rotting process
19 int[] directions = {1, 0, -1, 0, 1}; // Directions for adjacent cells: right, down, left, up
20
21 // Start BFS from initially rotten oranges
22 while (!queue.isEmpty() && freshOranges > 0) {
23 minutesElapsed++; // Increase time since each level of BFS represents 1 minute
24 for (int i = queue.size(); i > 0; --i) { // Iterate over rotten oranges at current time
25 int[] position = queue.poll();
26 for (int j = 0; j < 4; ++j) { // Check all adjacent cells
27 int x = position[0] + directions[j];
28 int y = position[1] + directions[j + 1];
29 // If adjacent cell is within bounds and has a fresh orange
30 if (x >= 0 && x < rows && y >= 0 && y < cols && grid[x][y] == 1) {
31 grid[x][y] = 2; // Rot the fresh orange
32 freshOranges--; // Decrement the fresh orange count
33 queue.offer(new int[] {x, y}); // Enqueue the new rotten orange's position
34 }
35 }
36 }
37 }
38
39 // If there are still fresh oranges left, return -1, otherwise return elapsed time
40 return freshOranges > 0 ? -1 : minutesElapsed;
41 }
42}
43
1#include <vector>
2#include <queue>
3using namespace std;
4
5class Solution {
6public:
7 // Function to calculate the number of minutes until no fresh orange left.
8 int orangesRotting(vector<vector<int>>& grid) {
9 int rows = grid.size(); // Number of rows in the grid
10 int cols = grid[0].size(); // Number of columns in the grid
11 int freshCount = 0; // Counter for fresh oranges
12 queue<pair<int, int>> rottenQueue; // Queue to maintain positions of rotten oranges
13
14 // Initialize the queue with all rotten oranges and count fresh ones.
15 for (int i = 0; i < rows; ++i) {
16 for (int j = 0; j < cols; ++j) {
17 if (grid[i][j] == 2) {
18 rottenQueue.emplace(i, j);
19 } else if (grid[i][j] == 1) {
20 ++freshCount;
21 }
22 }
23 }
24
25 // Time in minutes for rotting process.
26 int minutesElapsed = 0;
27 // Directions array that represents the 4 adjacent positions (up, right, down, left).
28 vector<int> directions = {-1, 0, 1, 0, -1};
29
30 // Processing the grid until the queue is empty or all fresh oranges are rotten.
31 while (!rottenQueue.empty() && freshCount > 0) {
32 ++minutesElapsed; // Increase time since we're moving to next minute
33
34 // Process all rotten oranges at the current time.
35 for (int i = rottenQueue.size(); i > 0; --i) {
36 // Fetch the coordinates of the current rotten orange.
37 auto position = rottenQueue.front();
38 rottenQueue.pop();
39 // Traverse 4 directions from the current rotten orange.
40 for (int j = 0; j < 4; ++j) {
41 int newRow = position.first + directions[j];
42 int newCol = position.second + directions[j + 1];
43 // Check boundary conditions and whether the position contains a fresh orange.
44 if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && grid[newRow][newCol] == 1) {
45 --freshCount; // Fresh orange becomes rotten
46 grid[newRow][newCol] = 2; // Update grid to reflect rotten orange
47 rottenQueue.emplace(newRow, newCol); // Add the new rotten orange to the queue
48 }
49 }
50 }
51 }
52
53 // Check if there are any fresh oranges left.
54 if (freshCount > 0) {
55 return -1; // Not all oranges can be rotten
56 } else {
57 return minutesElapsed; // All oranges are rotten, return total time taken
58 }
59 }
60};
61
1function orangesRotting(grid: number[][]): number {
2 const rows = grid.length;
3 const cols = grid[0].length;
4
5 let freshOranges = 0; // Counter for fresh oranges
6 const queue: [number, number][] = []; // Queue to store positions of rotten oranges
7
8 // Populate the queue with the initial positions of rotten oranges and count fresh oranges
9 for (let i = 0; i < rows; i++) {
10 for (let j = 0; j < cols; j++) {
11 if (grid[i][j] === 1) {
12 freshOranges++;
13 } else if (grid[i][j] === 2) {
14 queue.push([i, j]);
15 }
16 }
17 }
18
19 let minutes = 0; // Counter for elapsed minutes
20 // Directions for the adjacent cells (up, right, down, left)
21 const directions = [1, 0, -1, 0, 1];
22
23 // Loop until there are no more fresh oranges or the queue is empty
24 while (freshOranges !== 0 && queue.length !== 0) {
25 const currentLevelSize = queue.length; // Number of rotten oranges at the current minute
26
27 // Convert adjacent fresh oranges to rotten for each rotten orange in the queue
28 for (let i = 0; i < currentLevelSize; i++) {
29 const [x, y] = queue.shift(); // Get the next rotten orange position
30
31 // Check all four directions around the rotten orange
32 for (let j = 0; j < 4; j++) {
33 const newX = x + directions[j];
34 const newY = y + directions[j + 1];
35
36 // If the adjacent cell has a fresh orange, convert it to rotten
37 if (newX >= 0 && newX < rows && newY >= 0 && newY < cols && grid[newX][newY] === 1) {
38 grid[newX][newY] = 2;
39 queue.push([newX, newY]); // Add the new rotten orange position to the queue
40 freshOranges--; // Decrease the count of fresh oranges
41 }
42 }
43 }
44
45 minutes++; // Increment the minutes after each level of adjacency check
46 }
47
48 // If there are any remaining fresh oranges, return -1, otherwise return the elapsed minutes
49 return freshOranges !== 0 ? -1 : minutes;
50}
51
Time and Space Complexity
Time Complexity
The time complexity of the code is O(M * N)
, where M
is the number of rows and N
is the number of columns in the grid. This is because in the worst case, we may have to look at each cell in the grid. The while loop runs until all the rotten oranges have been processed. In the worst case, processing one rotten orange could affect 4 adjacent cells, but each cell is enqueued and dequeued only once. This gives us the worst-case scenario of processing M * N
cells.
Space Complexity
The space complexity of the code is also O(M * N)
. This is due to the queue which in the worst case might need to store all the cells if they are all rotten oranges. In addition to the queue, we have constant space for the variables m
, n
, cnt
, and ans
, but they do not scale with the size of the input, so the dominating factor is the space needed for the queue.
Learn more about how to find time and space complexity quickly using problem constraints.
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
Recommended Readings
https algomonster s3 us east 2 amazonaws com 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
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!
I don't think the posted solution is correct if we take the queue's size into consideration. Queue.size() will be dynamic and will prone to error. We need to store the size to be static.