Bird
Raised Fist0
Interview Prepgraphs-dfs-bfsmediumAmazonGoogleFacebookBloomberg

Rotting Oranges

Choose your preparation mode4 modes available
🎯
Rotting Oranges
mediumGRAPHAmazonGoogleFacebook

Imagine a warehouse full of fresh oranges where some start rotting. The rot spreads every minute to adjacent fresh oranges. How long until all oranges rot or determine if some remain fresh forever?

💡 This problem models a grid-based spread process, which is a classic use case for BFS on grids. Beginners often struggle to identify that multiple starting points (all rotten oranges) should be processed simultaneously, and that BFS naturally simulates the time progression.
📋
Problem Statement

You are given an m x n grid where each cell can have one of three values: 0 representing an empty cell, 1 representing a fresh orange, or 2 representing a rotten orange. Every minute, any fresh orange that is adjacent (4-directionally) to a rotten orange becomes rotten. Return the minimum number of minutes that must elapse until no cell has a fresh orange. If this is impossible, return -1.

1 ≤ m, n ≤ 10^2grid[i][j] is 0, 1, or 2
💡
Example
Input"[[2,1,1],[1,1,0],[0,1,1]]"
Output4

Rot spreads from the initial rotten orange at (0,0) to all fresh oranges in 4 minutes.

Input"[[2,1,1],[0,1,1],[1,0,1]]"
Output-1

Some fresh oranges cannot be reached by rot, so return -1.

Input"[[0,2]]"
Output0

No fresh oranges to rot, so time is 0.

  • Grid with no fresh oranges → output 0
  • Grid with no rotten oranges but fresh oranges present → output -1
  • Grid with all empty cells → output 0
  • Grid where fresh oranges are isolated by empty cells → output -1
⚠️
Common Mistakes
Not processing all rotten oranges simultaneously in BFS

Incorrect time calculation, rot spreads slower than it should

✅ Use a queue and process all rotten oranges at the current BFS level before incrementing time

Modifying grid while iterating without copying or queue control

Fresh oranges rot prematurely within the same iteration, causing incorrect results

✅ Use a queue to process level by level or copy grid before updating

Forgetting to check if fresh oranges remain after BFS

Returns incorrect time instead of -1 when some fresh oranges never rot

✅ After BFS, scan grid for any remaining fresh oranges and return -1 if found

Using DFS without pruning leading to stack overflow

Runtime error or very slow execution

✅ Implement pruning by tracking earliest rot time per cell to avoid repeated visits

Not handling edge cases like no fresh oranges or empty grid

Incorrect output or runtime errors

✅ Check and return 0 immediately if no fresh oranges at start

🧠
Brute Force (Simulate minute by minute with repeated scans)
💡 This approach simulates the process literally by scanning the grid repeatedly to find fresh oranges adjacent to rotten ones. It is intuitive but inefficient, helping beginners understand the problem dynamics before optimizing.

Intuition

At each minute, scan the entire grid to find fresh oranges adjacent to rotten ones and rot them. Repeat until no changes occur or all fresh oranges rot.

Algorithm

  1. Initialize time to 0.
  2. While fresh oranges remain and changes occur:
  3. Create a copy of the grid to track changes.
  4. For each cell, if it is fresh and adjacent to rotten, mark it rotten in the copy.
  5. If no changes in this iteration, break.
  6. Update the original grid with the copy and increment time.
  7. If fresh oranges remain, return -1; else return time.
💡 The challenge is to realize that scanning the entire grid repeatedly simulates the spread but is inefficient and can be slow for large grids.
</>
Code
from copy import deepcopy

def orangesRotting(grid):
    rows, cols = len(grid), len(grid[0])
    time = 0
    while True:
        changed = False
        new_grid = deepcopy(grid)
        for r in range(rows):
            for c in range(cols):
                if grid[r][c] == 1:
                    for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:
                        nr, nc = r + dr, c + dc
                        if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 2:
                            new_grid[r][c] = 2
                            changed = True
                            break
        if not changed:
            break
        grid = new_grid
        time += 1
    for row in grid:
        if 1 in row:
            return -1
    return time

# Example usage
if __name__ == '__main__':
    grid = [[2,1,1],[1,1,0],[0,1,1]]
    print(orangesRotting(grid))  # Output: 4
Line Notes
from copy import deepcopyNeeded to create a new grid copy to avoid modifying the grid while scanning
while True:Loop until no changes occur in a full scan
changed = FalseTrack if any fresh orange rots this iteration
new_grid = deepcopy(grid)Create a snapshot to apply changes simultaneously
for r in range(rows):Iterate over all rows to scan the grid
if grid[r][c] == 1:Focus only on fresh oranges to check adjacency
for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:Check all 4 adjacent directions
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 2:Ensure adjacency is valid and rotten
new_grid[r][c] = 2Mark fresh orange as rotten in the copy
if not changed:If no fresh orange rotted this round, stop
for row in grid:After simulation, check if any fresh oranges remain
import java.util.*;

public class RottingOranges {
    public static int orangesRotting(int[][] grid) {
        int rows = grid.length, cols = grid[0].length;
        int time = 0;
        while (true) {
            boolean changed = false;
            int[][] newGrid = new int[rows][cols];
            for (int i = 0; i < rows; i++)
                newGrid[i] = grid[i].clone();
            for (int r = 0; r < rows; r++) {
                for (int c = 0; c < cols; c++) {
                    if (grid[r][c] == 1) {
                        int[][] directions = {{1,0},{-1,0},{0,1},{0,-1}};
                        for (int[] d : directions) {
                            int nr = r + d[0], nc = c + d[1];
                            if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 2) {
                                newGrid[r][c] = 2;
                                changed = true;
                                break;
                            }
                        }
                    }
                }
            }
            if (!changed) break;
            grid = newGrid;
            time++;
        }
        for (int[] row : grid) {
            for (int val : row) {
                if (val == 1) return -1;
            }
        }
        return time;
    }

    public static void main(String[] args) {
        int[][] grid = {{2,1,1},{1,1,0},{0,1,1}};
        System.out.println(orangesRotting(grid)); // Output: 4
    }
}
Line Notes
int time = 0;Initialize time counter for minutes elapsed
while (true) {Loop until no fresh orange rots in an iteration
int[][] newGrid = new int[rows][cols];Create a copy to apply changes simultaneously
newGrid[i] = grid[i].clone();Clone each row to preserve original grid during iteration
if (grid[r][c] == 1) {Only check fresh oranges for adjacency
for (int[] d : directions) {Check all four adjacent cells
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 2) {Validate adjacency and rotten status
if (!changed) break;Stop if no fresh orange rotted this iteration
for (int val : row) { if (val == 1) return -1; }Check if any fresh oranges remain after simulation
#include <iostream>
#include <vector>
using namespace std;

int orangesRotting(vector<vector<int>>& grid) {
    int rows = grid.size(), cols = grid[0].size();
    int time = 0;
    while (true) {
        bool changed = false;
        vector<vector<int>> newGrid = grid;
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                if (grid[r][c] == 1) {
                    int directions[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
                    for (auto& d : directions) {
                        int nr = r + d[0], nc = c + d[1];
                        if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 2) {
                            newGrid[r][c] = 2;
                            changed = true;
                            break;
                        }
                    }
                }
            }
        }
        if (!changed) break;
        grid = newGrid;
        time++;
    }
    for (auto& row : grid) {
        for (int val : row) {
            if (val == 1) return -1;
        }
    }
    return time;
}

int main() {
    vector<vector<int>> grid = {{2,1,1},{1,1,0},{0,1,1}};
    cout << orangesRotting(grid) << endl; // Output: 4
    return 0;
}
Line Notes
int time = 0;Initialize minute counter
while (true) {Repeat until no fresh orange rots
vector<vector<int>> newGrid = grid;Copy grid to apply changes simultaneously
if (grid[r][c] == 1) {Check only fresh oranges
int directions[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};Four adjacent directions
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 2) {Check valid adjacency and rotten status
if (!changed) break;Stop if no changes this iteration
for (auto& row : grid) { for (int val : row) { if (val == 1) return -1; } }Check for remaining fresh oranges
function orangesRotting(grid) {
    const rows = grid.length, cols = grid[0].length;
    let time = 0;
    while (true) {
        let changed = false;
        const newGrid = grid.map(row => row.slice());
        for (let r = 0; r < rows; r++) {
            for (let c = 0; c < cols; c++) {
                if (grid[r][c] === 1) {
                    const directions = [[1,0],[-1,0],[0,1],[0,-1]];
                    for (const [dr, dc] of directions) {
                        const nr = r + dr, nc = c + dc;
                        if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 2) {
                            newGrid[r][c] = 2;
                            changed = true;
                            break;
                        }
                    }
                }
            }
        }
        if (!changed) break;
        grid = newGrid;
        time++;
    }
    for (const row of grid) {
        if (row.includes(1)) return -1;
    }
    return time;
}

// Example usage
console.log(orangesRotting([[2,1,1],[1,1,0],[0,1,1]])); // Output: 4
Line Notes
let time = 0;Initialize time counter
while (true) {Loop until no fresh orange rots
const newGrid = grid.map(row => row.slice());Deep copy grid to apply changes simultaneously
if (grid[r][c] === 1) {Focus on fresh oranges only
const directions = [[1,0],[-1,0],[0,1],[0,-1]];Four directions to check adjacency
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 2) {Check valid adjacent rotten orange
if (!changed) break;Stop if no fresh orange rotted this iteration
for (const row of grid) { if (row.includes(1)) return -1; }Check if any fresh oranges remain after simulation
⏱
Complexity
TimeO(m * n * T) where T is number of minutes until no change or max
SpaceO(m * n) for grid copy

Each iteration scans the entire grid (m*n), and in worst case, rot spreads one cell per minute, so T can be up to m*n

💡 For a 10x10 grid, this could mean up to 100 iterations scanning 100 cells each, totaling 10,000 operations, which is inefficient.
Interview Verdict: TLE / Inefficient for large grids

This approach is too slow for large inputs but helps understand the problem and simulates the process literally.

🧠
Better Approach (Multi-source BFS)
💡 This approach uses BFS starting simultaneously from all rotten oranges, simulating the rot spread minute by minute efficiently. It avoids repeated full scans by processing only the frontier of rotten oranges.

Intuition

Put all initially rotten oranges into a queue. Each minute, rot adjacent fresh oranges and add them to the queue. The BFS level count corresponds to minutes elapsed.

Algorithm

  1. Initialize a queue and add all rotten oranges' positions.
  2. Count total fresh oranges.
  3. Initialize time to 0.
  4. While queue not empty and fresh oranges remain:
  5. For each orange in current queue level:
  6. Rot adjacent fresh oranges, decrement fresh count, add them to queue.
  7. Increment time after processing each level.
  8. If fresh oranges remain, return -1; else return time.
💡 The key is BFS level order traversal where each level corresponds to one minute of rot spreading.
</>
Code
from collections import deque

def orangesRotting(grid):
    rows, cols = len(grid), len(grid[0])
    queue = deque()
    fresh = 0
    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 += 1
    if fresh == 0:
        return 0
    time = 0
    directions = [(1,0),(-1,0),(0,1),(0,-1)]
    while queue and fresh > 0:
        for _ in range(len(queue)):
            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 -= 1
                    queue.append((nr,nc))
        time += 1
    return time if fresh == 0 else -1

# Example usage
if __name__ == '__main__':
    grid = [[2,1,1],[1,1,0],[0,1,1]]
    print(orangesRotting(grid))  # Output: 4
Line Notes
queue = deque()Initialize queue for BFS processing
if grid[r][c] == 2: queue.append((r,c))Add all initially rotten oranges as BFS sources
elif grid[r][c] == 1: fresh += 1Count total fresh oranges to track progress
while queue and fresh > 0:Process BFS levels until no fresh oranges remain or no more spread possible
for _ in range(len(queue)):Process all rotten oranges at current BFS level (minute)
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:Check valid adjacent fresh orange
grid[nr][nc] = 2Mark fresh orange as rotten
fresh -= 1Decrease count of fresh oranges
queue.append((nr,nc))Add newly rotten orange to queue for next level
time += 1Increment time after processing each BFS level
import java.util.*;

public class RottingOranges {
    public static int orangesRotting(int[][] grid) {
        int rows = grid.length, cols = grid[0].length;
        Queue<int[]> queue = new LinkedList<>();
        int fresh = 0;
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                if (grid[r][c] == 2) queue.offer(new int[]{r,c});
                else if (grid[r][c] == 1) fresh++;
            }
        }
        if (fresh == 0) return 0;
        int time = 0;
        int[][] directions = {{1,0},{-1,0},{0,1},{0,-1}};
        while (!queue.isEmpty() && fresh > 0) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                int[] pos = queue.poll();
                int r = pos[0], c = pos[1];
                for (int[] d : directions) {
                    int nr = r + d[0], nc = c + d[1];
                    if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 1) {
                        grid[nr][nc] = 2;
                        fresh--;
                        queue.offer(new int[]{nr,nc});
                    }
                }
            }
            time++;
        }
        return fresh == 0 ? time : -1;
    }

    public static void main(String[] args) {
        int[][] grid = {{2,1,1},{1,1,0},{0,1,1}};
        System.out.println(orangesRotting(grid)); // Output: 4
    }
}
Line Notes
Queue<int[]> queue = new LinkedList<>();Initialize BFS queue
if (grid[r][c] == 2) queue.offer(new int[]{r,c});Add all rotten oranges as BFS sources
else if (grid[r][c] == 1) fresh++;Count fresh oranges
while (!queue.isEmpty() && fresh > 0) {Process BFS levels until no fresh oranges remain
int size = queue.size();Number of rotten oranges to process this minute
for (int i = 0; i < size; i++) {Process current BFS level
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 1) {Check valid adjacent fresh orange
grid[nr][nc] = 2; fresh--; queue.offer(new int[]{nr,nc});Rot fresh orange and add to queue
time++;Increment time after processing each BFS level
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

int orangesRotting(vector<vector<int>>& grid) {
    int rows = grid.size(), 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});
            else if (grid[r][c] == 1) fresh++;
        }
    }
    if (fresh == 0) return 0;
    int time = 0;
    int directions[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
    while (!q.empty() && fresh > 0) {
        int size = q.size();
        for (int i = 0; i < size; i++) {
            auto [r, c] = q.front(); q.pop();
            for (auto& d : directions) {
                int nr = r + d[0], nc = c + d[1];
                if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 1) {
                    grid[nr][nc] = 2;
                    fresh--;
                    q.push({nr,nc});
                }
            }
        }
        time++;
    }
    return fresh == 0 ? time : -1;
}

int main() {
    vector<vector<int>> grid = {{2,1,1},{1,1,0},{0,1,1}};
    cout << orangesRotting(grid) << endl; // Output: 4
    return 0;
}
Line Notes
queue<pair<int,int>> q;Queue for BFS processing
if (grid[r][c] == 2) q.push({r,c});Add rotten oranges as BFS sources
else if (grid[r][c] == 1) fresh++;Count fresh oranges
while (!q.empty() && fresh > 0) {Process BFS levels until no fresh oranges remain
int size = q.size();Number of rotten oranges to process this minute
for (int i = 0; i < size; i++) {Process current BFS level
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 1) {Check valid adjacent fresh orange
grid[nr][nc] = 2; fresh--; q.push({nr,nc});Rot fresh orange and add to queue
time++;Increment time after processing each BFS level
function orangesRotting(grid) {
    const rows = grid.length, cols = grid[0].length;
    const queue = [];
    let fresh = 0;
    for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
            if (grid[r][c] === 2) queue.push([r,c]);
            else if (grid[r][c] === 1) fresh++;
        }
    }
    if (fresh === 0) return 0;
    let time = 0;
    const directions = [[1,0],[-1,0],[0,1],[0,-1]];
    while (queue.length > 0 && fresh > 0) {
        const size = queue.length;
        for (let i = 0; i < size; i++) {
            const [r, c] = queue.shift();
            for (const [dr, dc] of directions) {
                const nr = r + dr, nc = c + dc;
                if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 1) {
                    grid[nr][nc] = 2;
                    fresh--;
                    queue.push([nr,nc]);
                }
            }
        }
        time++;
    }
    return fresh === 0 ? time : -1;
}

// Example usage
console.log(orangesRotting([[2,1,1],[1,1,0],[0,1,1]])); // Output: 4
Line Notes
const queue = [];Initialize BFS queue
if (grid[r][c] === 2) queue.push([r,c]);Add rotten oranges as BFS sources
else if (grid[r][c] === 1) fresh++;Count fresh oranges
while (queue.length > 0 && fresh > 0) {Process BFS levels until no fresh oranges remain
const size = queue.length;Number of rotten oranges to process this minute
for (let i = 0; i < size; i++) {Process current BFS level
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 1) {Check valid adjacent fresh orange
grid[nr][nc] = 2; fresh--; queue.push([nr,nc]);Rot fresh orange and add to queue
time++;Increment time after processing each BFS level
⏱
Complexity
TimeO(m * n)
SpaceO(m * n)

Each cell is processed at most once when it becomes rotten, so total operations proportional to grid size

💡 For a 10x10 grid, this means at most 100 operations, which is efficient and scalable.
Interview Verdict: Accepted / Optimal for this problem

This approach is efficient and the standard solution for this problem in interviews.

🧠
Alternative Approach (DFS with time tracking)
💡 DFS can be used to simulate rot spread by recursively rotting adjacent fresh oranges and tracking the time taken. However, it is less natural and can be inefficient due to repeated visits and stack depth.

Intuition

For each rotten orange, recursively rot adjacent fresh oranges, passing the current time + 1. Track the maximum time needed to rot all oranges.

Algorithm

  1. Initialize max_time to 0.
  2. For each rotten orange, call DFS with time 0.
  3. In DFS, if current cell is fresh or can be rotted earlier, update its time and recurse to neighbors with time + 1.
  4. After DFS, check if any fresh orange remains; if yes, return -1 else return max_time.
💡 DFS requires careful time tracking and pruning to avoid infinite recursion and repeated work.
</>
Code
def orangesRotting(grid):
    rows, cols = len(grid), len(grid[0])
    time_grid = [[-1]*cols for _ in range(rows)]
    max_time = 0

    def dfs(r, c, time):
        nonlocal max_time
        if r < 0 or r >= rows or c < 0 or c >= cols:
            return
        if grid[r][c] == 0:
            return
        if time_grid[r][c] != -1 and time_grid[r][c] <= time:
            return
        time_grid[r][c] = time
        max_time = max(max_time, time)
        directions = [(1,0),(-1,0),(0,1),(0,-1)]
        for dr, dc in directions:
            nr, nc = r + dr, c + dc
            if 0 <= nr < rows and 0 <= nc < cols:
                if grid[nr][nc] == 1 or (grid[nr][nc] == 2 and time_grid[nr][nc] > time + 1):
                    dfs(nr, nc, time + 1)

    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == 2:
                dfs(r, c, 0)

    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == 1 and time_grid[r][c] == -1:
                return -1
    return max_time

# Example usage
if __name__ == '__main__':
    grid = [[2,1,1],[1,1,0],[0,1,1]]
    print(orangesRotting(grid))  # Output: 4
Line Notes
time_grid = [[-1]*cols for _ in range(rows)]Track earliest time each cell gets rotten
def dfs(r, c, time):Recursive DFS function with current time
if time_grid[r][c] != -1 and time_grid[r][c] <= time:Prune if cell already rotted earlier or same time
time_grid[r][c] = timeUpdate time of rotting for current cell
max_time = max(max_time, time)Track maximum time across all cells
for dr, dc in directions:Explore all 4 adjacent cells
if 0 <= nr < rows and 0 <= nc < cols:Check boundaries before accessing grid
if grid[nr][nc] == 1 or (grid[nr][nc] == 2 and time_grid[nr][nc] > time + 1):Recurse if fresh or can be rotted earlier
for r in range(rows): for c in range(cols): if grid[r][c] == 2: dfs(r, c, 0)Start DFS from all rotten oranges
if grid[r][c] == 1 and time_grid[r][c] == -1: return -1If fresh orange never rotted, return -1
import java.util.*;

public class RottingOranges {
    static int maxTime = 0;
    static int rows, cols;
    static int[][] timeGrid;
    static int[][] grid;
    static int[][] directions = {{1,0},{-1,0},{0,1},{0,-1}};

    public static void dfs(int r, int c, int time) {
        if (r < 0 || r >= rows || c < 0 || c >= cols) return;
        if (grid[r][c] == 0) return;
        if (timeGrid[r][c] != -1 && timeGrid[r][c] <= time) return;
        timeGrid[r][c] = time;
        maxTime = Math.max(maxTime, time);
        for (int[] d : directions) {
            int nr = r + d[0], nc = c + d[1];
            if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {
                if (grid[nr][nc] == 1 || (grid[nr][nc] == 2 && timeGrid[nr][nc] > time + 1)) {
                    dfs(nr, nc, time + 1);
                }
            }
        }
    }

    public static int orangesRotting(int[][] inputGrid) {
        grid = inputGrid;
        rows = grid.length;
        cols = grid[0].length;
        timeGrid = new int[rows][cols];
        for (int[] row : timeGrid) Arrays.fill(row, -1);
        maxTime = 0;
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                if (grid[r][c] == 2) dfs(r, c, 0);
            }
        }
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                if (grid[r][c] == 1 && timeGrid[r][c] == -1) return -1;
            }
        }
        return maxTime;
    }

    public static void main(String[] args) {
        int[][] grid = {{2,1,1},{1,1,0},{0,1,1}};
        System.out.println(orangesRotting(grid)); // Output: 4
    }
}
Line Notes
static int[][] timeGrid;Track earliest time each cell gets rotten
if (timeGrid[r][c] != -1 && timeGrid[r][c] <= time) return;Prune DFS if cell rotted earlier or same time
timeGrid[r][c] = time;Update rotting time for current cell
maxTime = Math.max(maxTime, time);Track maximum time across all cells
for (int[] d : directions) {Explore all 4 adjacent cells
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {Check boundaries before accessing grid
if (grid[nr][nc] == 1 || (grid[nr][nc] == 2 && timeGrid[nr][nc] > time + 1)) {Recurse if fresh or can be rotted earlier
for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { if (grid[r][c] == 2) dfs(r, c, 0); } }Start DFS from all rotten oranges
if (grid[r][c] == 1 && timeGrid[r][c] == -1) return -1;Return -1 if fresh orange never rotted
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int maxTime = 0;
int rows, cols;
vector<vector<int>> timeGrid;
vector<vector<int>> grid;
int directions[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};

void dfs(int r, int c, int time) {
    if (r < 0 || r >= rows || c < 0 || c >= cols) return;
    if (grid[r][c] == 0) return;
    if (timeGrid[r][c] != -1 && timeGrid[r][c] <= time) return;
    timeGrid[r][c] = time;
    maxTime = max(maxTime, time);
    for (auto& d : directions) {
        int nr = r + d[0], nc = c + d[1];
        if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {
            if (grid[nr][nc] == 1 || (grid[nr][nc] == 2 && timeGrid[nr][nc] > time + 1)) {
                dfs(nr, nc, time + 1);
            }
        }
    }
}

int orangesRotting(vector<vector<int>>& inputGrid) {
    grid = inputGrid;
    rows = grid.size();
    cols = grid[0].size();
    timeGrid.assign(rows, vector<int>(cols, -1));
    maxTime = 0;
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            if (grid[r][c] == 2) dfs(r, c, 0);
        }
    }
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            if (grid[r][c] == 1 && timeGrid[r][c] == -1) return -1;
        }
    }
    return maxTime;
}

int main() {
    vector<vector<int>> grid = {{2,1,1},{1,1,0},{0,1,1}};
    cout << orangesRotting(grid) << endl; // Output: 4
    return 0;
}
Line Notes
vector<vector<int>> timeGrid;Track earliest time each cell gets rotten
if (timeGrid[r][c] != -1 && timeGrid[r][c] <= time) return;Prune DFS if cell rotted earlier or same time
timeGrid[r][c] = time;Update rotting time for current cell
maxTime = max(maxTime, time);Track maximum time across all cells
for (auto& d : directions) {Explore all 4 adjacent cells
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {Check boundaries before accessing grid
if (grid[nr][nc] == 1 || (grid[nr][nc] == 2 && timeGrid[nr][nc] > time + 1)) {Recurse if fresh or can be rotted earlier
for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { if (grid[r][c] == 2) dfs(r, c, 0); } }Start DFS from all rotten oranges
if (grid[r][c] == 1 && timeGrid[r][c] == -1) return -1;Return -1 if fresh orange never rotted
function orangesRotting(grid) {
    const rows = grid.length, cols = grid[0].length;
    const timeGrid = Array.from({length: rows}, () => Array(cols).fill(-1));
    let maxTime = 0;

    function dfs(r, c, time) {
        if (r < 0 || r >= rows || c < 0 || c >= cols) return;
        if (grid[r][c] === 0) return;
        if (timeGrid[r][c] !== -1 && timeGrid[r][c] <= time) return;
        timeGrid[r][c] = time;
        maxTime = Math.max(maxTime, time);
        const directions = [[1,0],[-1,0],[0,1],[0,-1]];
        for (const [dr, dc] of directions) {
            const nr = r + dr, nc = c + dc;
            if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {
                if (grid[nr][nc] === 1 || (grid[nr][nc] === 2 && timeGrid[nr][nc] > time + 1)) {
                    dfs(nr, nc, time + 1);
                }
            }
        }
    }

    for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
            if (grid[r][c] === 2) dfs(r, c, 0);
        }
    }

    for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
            if (grid[r][c] === 1 && timeGrid[r][c] === -1) return -1;
        }
    }
    return maxTime;
}

// Example usage
console.log(orangesRotting([[2,1,1],[1,1,0],[0,1,1]])); // Output: 4
Line Notes
const timeGrid = Array.from({length: rows}, () => Array(cols).fill(-1));Track earliest time each cell gets rotten
if (timeGrid[r][c] !== -1 && timeGrid[r][c] <= time) return;Prune DFS if cell rotted earlier or same time
timeGrid[r][c] = time;Update rotting time for current cell
maxTime = Math.max(maxTime, time);Track maximum time across all cells
for (const [dr, dc] of directions) {Explore all 4 adjacent cells
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {Check boundaries before accessing grid
if (grid[nr][nc] === 1 || (grid[nr][nc] === 2 && timeGrid[nr][nc] > time + 1)) {Recurse if fresh or can be rotted earlier
for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (grid[r][c] === 2) dfs(r, c, 0); } }Start DFS from all rotten oranges
if (grid[r][c] === 1 && timeGrid[r][c] === -1) return -1;Return -1 if fresh orange never rotted
⏱
Complexity
TimeO(m * n * 4) worst case due to recursive calls and pruning
SpaceO(m * n) for recursion stack and time grid

DFS may revisit cells multiple times but pruning limits redundant calls; still less efficient than BFS

💡 For a 10x10 grid, recursion depth and repeated calls can cause stack overflow or inefficiency.
Interview Verdict: Accepted but less efficient and less natural than BFS

DFS works but is not the preferred approach due to complexity and potential stack issues.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, always code the BFS approach as it is optimal and clean. Brute force is useful to explain your understanding, and DFS is rarely preferred here.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(m * n * T) with T up to m*nO(m * n) for grid copyNoN/AMention only - never code due to inefficiency
2. Multi-source BFSO(m * n)O(m * n) for queueNoN/ACode this approach in 95% of interviews
3. DFS with time trackingO(m * n * 4) worst caseO(m * n) recursion stackYesN/AMention as alternative; rarely code
💼
Interview Strategy
💡 Use this guide to understand the problem deeply before coding. Start by clarifying the problem, then explain the brute force approach to show understanding. Next, present the BFS solution as the optimal approach. Finally, discuss edge cases and test your code thoroughly.

How to Present

Step 1: Clarify the problem and constraints with the interviewer.Step 2: Describe the brute force simulation approach to show understanding.Step 3: Introduce the multi-source BFS approach as an optimization.Step 4: Code the BFS solution carefully, explaining each step.Step 5: Test with provided examples and edge cases.Step 6: Discuss possible follow-ups or alternative approaches.

Time Allocation

Clarify: 2min → Approach: 3min → Code: 10min → Test: 5min. Total ~20min

What the Interviewer Tests

The interviewer tests your ability to recognize multi-source BFS on grids, handle edge cases, and write clean, efficient code. They also check if you can explain your approach clearly and optimize from brute force to BFS.

Common Follow-ups

  • What if diagonals also spread rot? → Add diagonal directions to BFS.
  • Can you do it in-place without extra space? → Modify grid directly and use queue only.
  • What if grid is very large? → BFS still efficient; consider memory constraints.
  • How to find which oranges never rot? → Track unreachable fresh oranges after BFS.
💡 These follow-ups test your flexibility in adapting BFS and understanding problem variations.
🔍
Pattern Recognition

When to Use

1) Grid or matrix input, 2) Spread or contagion process, 3) Multiple starting points, 4) Need minimum time or steps

Signature Phrases

'adjacent fresh oranges become rotten every minute''minimum number of minutes until no fresh orange remains'

NOT This Pattern When

Problems involving only single-source BFS or unrelated graph traversal without time simulation

Similar Problems

01 Matrix - BFS to find shortest distance to zero cellsWalls and Gates - multi-source BFS to fill distancesNumber of Islands - DFS/BFS to find connected components

Practice

(1/5)
1. Consider the following Python code implementing Kahn's Algorithm for course scheduling. What is the return value of canFinish(3, [[1,0],[2,1]]) after the while loop completes?
easy
A. false
B. true
C. Raises an error due to empty queue
D. Returns true only if numCourses is 2

Solution

  1. Step 1: Initialize graph and in-degree arrays

    Graph: 0 -> [1], 1 -> [2], 2 -> []. In-degree: [0,1,1]. Queue starts with nodes having in-degree 0 -> [0].
  2. Step 2: Process queue and update count

    Pop 0, count=1; decrement in-degree of 1 to 0, enqueue 1. Pop 1, count=2; decrement in-degree of 2 to 0, enqueue 2. Pop 2, count=3; queue empty.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    All courses processed, so return true [OK]
Hint: Count equals numCourses means no cycle [OK]
Common Mistakes:
  • Off-by-one error in count increment
  • Misunderstanding queue initialization
  • Confusing in-degree decrement timing
2. You are given a directed acyclic graph with n nodes and a list of edges. You want to find the smallest set of nodes such that starting a traversal from these nodes can reach every other node in the graph. Which approach guarantees finding this minimal set efficiently?
easy
A. Perform BFS from each node and pick the nodes that can reach all others.
B. Identify all nodes with zero in-degree and return them as the minimal set.
C. Identify all nodes with zero in-degree and return them as the minimal set.
D. Use a dynamic programming approach to count paths from each node.

Solution

  1. Step 1: Understand the problem constraints

    The problem requires the minimal set of vertices from which all nodes are reachable. Nodes with zero in-degree are exactly those that no other node reaches, so they must be included.
  2. Step 2: Analyze approaches

    Performing BFS from each node is inefficient. Dynamic programming counting paths is unnecessary. The minimal set is precisely all nodes with zero in-degree.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Minimal set corresponds exactly to zero in-degree nodes [OK]
Hint: Minimal set corresponds exactly to zero in-degree nodes [OK]
Common Mistakes:
  • Assuming BFS from each node is efficient
  • Confusing zero in-degree nodes with minimal set without justification
  • Using DP which is unnecessary here
3. What is the time complexity of the optimal approach using a single trust score array to find the town judge, given n people and m trust pairs?
medium
A. O(n * m) because we scan trust pairs for each person
B. O(n^2) because we check trust relationships pairwise
C. O(m^2) because trust pairs are nested in loops
D. O(n + m) because we process each trust pair once and then scan all people once

Solution

  1. Step 1: Identify loops in the algorithm

    The algorithm iterates once over all trust pairs (m) and then once over all people (n).
  2. Step 2: Combine complexities

    Processing trust pairs is O(m), scanning people is O(n), total O(n + m).
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Single pass over trust and people arrays [OK]
Hint: Single pass over trust pairs and people yields O(n + m) [OK]
Common Mistakes:
  • Assuming nested loops cause O(n*m)
  • Confusing trust pairs count with people count
  • Ignoring linear scans after trust processing
4. Examine the following buggy code snippet for bidirectional BFS to find the shortest path in a binary matrix. Which line contains the subtle bug that can cause infinite loops or incorrect results?
def visit(queue, visited, other_visited):
    r, c = queue.popleft()
    for dr, dc in directions:
        nr, nc = r + dr, c + dc
        if 0 <= nr < n and 0 <= nc < n and grid[nr][nc] == 0:
            if (nr, nc) in other_visited:
                return visited[(r, c)] + other_visited[(nr, nc)]
            if (nr, nc) not in visited:
                queue.append((nr, nc))  # Bug here
                # Missing visited marking here
    return None
medium
A. Line where the current cell is popped from the queue
B. Line where the cell is checked if in other_visited
C. Line where neighbors are checked for grid boundaries
D. Line where neighbor is appended to queue without marking visited

Solution

  1. Step 1: Identify visited marking usage

    Visited must be marked when neighbor is enqueued to avoid revisiting.
  2. Step 2: Locate missing visited marking

    Neighbor is appended to queue but not marked visited, causing possible infinite revisits.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Marking visited on enqueue prevents infinite loops [OK]
Hint: Mark visited when enqueuing neighbors [OK]
Common Mistakes:
  • Mark visited only when dequeuing
  • Forgetting visited marking entirely
5. Suppose the Keys and Rooms problem is modified so that keys can be reused infinitely (i.e., you can revisit rooms multiple times). Which of the following changes to the algorithm is necessary to correctly determine if all rooms can be visited?
hard
A. Remove the visited array and perform DFS without tracking visited rooms
B. Use BFS with a queue and mark rooms as visited when enqueued to avoid infinite loops
C. Keep the visited array but reset it after each DFS call to allow revisits
D. Use a counter to limit the number of visits per room to prevent infinite recursion

Solution

  1. Step 1: Understand infinite reuse of keys means cycles are possible

    Without marking visited before enqueueing, BFS can enqueue the same room multiple times causing inefficiency or infinite loops.
  2. Step 2: Use BFS with visited marking on enqueue

    Marking visited when enqueuing prevents multiple enqueues of the same room, ensuring termination and correctness.
  3. Step 3: Why other options fail

    Removing visited causes infinite loops; resetting visited loses progress; limiting visits is inefficient and complex.
  4. Final Answer:

    Option B -> Option B
  5. Quick Check:

    BFS with visited marking on enqueue is standard for graphs with cycles [OK]
Hint: Mark visited when enqueuing in BFS to handle cycles [OK]
Common Mistakes:
  • Removing visited causes infinite loops
  • Resetting visited loses track of progress
  • Counting visits is inefficient and unnecessary
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard