Create a time grid initialized to -1 for all cells, indicating no rot time assigned yet. Initialize max_time to 0 to track the longest rot spread time.
💡 This setup prepares a parallel grid to record when each orange becomes rotten, essential for tracking propagation timing.
Line:time_grid = [[-1]*cols for _ in range(rows)]
max_time = 0
💡 We need a separate structure to store rot times without modifying the original grid.
traverse
Start DFS from initial rotten orange at (0,0) with time 0
Begin DFS at cell (0,0), which is initially rotten. Assign time 0 to this cell in the time grid and update max_time to 0.
💡 Starting DFS from rotten oranges seeds the rot spread and sets the baseline time for propagation.
Line:if grid[r][c] == 2:
dfs(r, c, 0)
💡 Rot starts spreading from all initially rotten oranges at time 0.
traverse
DFS explores neighbor (1,0) from (0,0) with time 1
From (0,0), DFS moves to neighbor (1,0) which is fresh. Since it has no rot time yet, assign time 1 and update max_time to 1.
💡 DFS spreads rot to adjacent fresh oranges, incrementing time to represent minutes passed.
Line:for dr, dc in directions:
nr, nc = r + dr, c + dc
if grid[nr][nc] == 1:
dfs(nr, nc, time + 1)
💡 Rot spreads one minute later to adjacent fresh oranges.
traverse
DFS explores neighbor (1,1) from (1,0) with time 2
From (1,0), DFS moves to neighbor (1,1) which is fresh. Assign time 2 and update max_time to 2.
💡 DFS continues spreading rot deeper into the grid, increasing time accordingly.
Line:for dr, dc in directions:
nr, nc = r + dr, c + dc
if grid[nr][nc] == 1:
dfs(nr, nc, time + 1)
💡 Rot time accumulates as DFS explores further neighbors.
traverse
DFS explores neighbor (2,1) from (1,1) with time 3
From (1,1), DFS moves to neighbor (2,1) which is fresh. Assign time 3 and update max_time to 3.
💡 DFS continues rot spread downwards, incrementing time as it goes.
Line:for dr, dc in directions:
nr, nc = r + dr, c + dc
if grid[nr][nc] == 1:
dfs(nr, nc, time + 1)
💡 Rot time reflects minutes passed as DFS explores neighbors.
traverse
DFS explores neighbor (2,2) from (2,1) with time 4
From (2,1), DFS moves to neighbor (2,2) which is fresh. Assign time 4 and update max_time to 4.
💡 DFS reaches the farthest fresh orange reachable, marking the longest rot time.
Line:for dr, dc in directions:
nr, nc = r + dr, c + dc
if grid[nr][nc] == 1:
dfs(nr, nc, time + 1)
💡 The maximum rot time corresponds to the longest path rot must travel.
traverse
DFS backtracks from (2,2) to explore other neighbors
No further fresh neighbors from (2,2), so DFS backtracks to (2,1) to check other neighbors.
💡 DFS backtracking allows exploring all possible rot paths to update times correctly.
Line:return (implicit at end of dfs call)
💡 Backtracking ensures all neighbors are explored for earliest rot times.
traverse
DFS explores neighbor (0,1) from (0,0) with time 1
Back at (0,0), DFS explores neighbor (0,1) which is fresh. Assign time 1 and update max_time remains 4.
💡 DFS explores all neighbors of initial rotten orange to propagate rot in all directions.
Line:for dr, dc in directions:
nr, nc = r + dr, c + dc
if grid[nr][nc] == 1:
dfs(nr, nc, time + 1)
💡 Rot spreads simultaneously in multiple directions from the source.
traverse
DFS explores neighbor (0,2) from (0,1) with time 2
From (0,1), DFS moves to neighbor (0,2) which is fresh. Assign time 2 and max_time remains 4.
💡 DFS continues spreading rot along this branch, updating times accordingly.
Line:dfs(nr, nc, time + 1)
💡 Rot time is updated only if earlier than previous or unvisited.
traverse
DFS backtracks and finishes all reachable nodes
DFS backtracks from (0,2) and completes all calls. No more fresh oranges reachable from initial rotten orange.
💡 DFS completes after exploring all reachable fresh oranges, finalizing rot times.
Line:return (implicit at end of dfs calls)
💡 All reachable fresh oranges have been assigned earliest rot times.
prune
Check for unreachable fresh oranges
Scan the grid to find any fresh orange with no rot time assigned (-1). None found, so all fresh oranges are reachable.
💡 This step verifies if the rot spread reached all fresh oranges, determining if the answer is valid.
Line:for r in range(rows):
for c in range(cols):
if grid[r][c] == 1 and time_grid[r][c] == -1:
return -1
💡 If any fresh orange is unreachable, the answer is -1; here all are reachable.
reconstruct
Return max_time as the total minutes to rot all oranges
Return the maximum time recorded in the time grid, which is 4, representing the minutes needed to rot all fresh oranges.
💡 The final answer is the longest time it took for rot to reach any fresh orange.
Line:return max_time
💡 The answer reflects the slowest rot spread path in the grid.
def orangesRotting(grid):
rows, cols = len(grid), len(grid[0])
time_grid = [[-1]*cols for _ in range(rows)] # STEP 1
max_time = 0 # STEP 1
def dfs(r, c, time): # STEP 2 and onwards
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): # STEP 2
for c in range(cols):
if grid[r][c] == 2:
dfs(r, c, 0)
for r in range(rows): # STEP 11
for c in range(cols):
if grid[r][c] == 1 and time_grid[r][c] == -1:
return -1
return max_time # STEP 12
if __name__ == '__main__':
grid = [[2,1,1],[1,1,0],[0,1,1]]
print(orangesRotting(grid)) # Output: 4
📊
Rotting Oranges - Watch the Algorithm Execute, Step by Step
Watching the step-by-step DFS traversal with time tracking reveals how the algorithm propagates rot and updates times, which is hard to grasp from code alone.
Step 1/12
·Active fill★Answer cell
Visiting: none
0,0
0,1
0,2
1,0
1,1
1,2
2,0
2,1
2,2
Visiting: 0,0
0,0
0,1
0,2
1,0
1,1
1,2
2,0
2,1
2,2
Queue / Stack
0,0
Visiting: 1,0
Edge: 0,0 → 1,0
0,0
1,0
0,1
0,2
1,1
1,2
2,0
2,1
2,2
Queue / Stack
1,00,0
Visiting: 1,1
Edge: 1,0 → 1,1
0,0
1,0
1,1
0,1
0,2
1,2
2,0
2,1
2,2
Queue / Stack
1,11,00,0
Visiting: 2,1
Edge: 1,1 → 2,1
0,0
1,0
1,1
2,1
0,1
0,2
1,2
2,0
2,2
Queue / Stack
2,11,11,00,0
Visiting: 2,2
Edge: 2,1 → 2,2
0,0
1,0
1,1
2,1
2,2
0,1
0,2
1,2
2,0
Queue / Stack
2,22,11,11,00,0
Visiting: 2,1
2,2
2,1
1,1
1,0
0,0
0,1
0,2
1,2
2,0
Queue / Stack
2,11,11,00,0
Visiting: 0,1
Edge: 0,0 → 0,1
0,0
0,1
0,2
1,0
1,1
2,1
2,2
1,2
2,0
Queue / Stack
0,10,0
Visiting: 0,2
Edge: 0,1 → 0,2
0,0
0,1
0,2
1,0
1,1
2,1
2,2
1,2
2,0
Queue / Stack
0,20,10,0
Visiting: none
0,0
0,1
0,2
1,0
1,1
2,1
2,2
1,2
2,0
Visiting: none
0,0
0,1
0,2
1,0
1,1
2,1
2,2
1,2
2,0
Visiting: none
0,0
0,1
0,2
1,0
1,1
2,1
2,2
1,2
2,0
Key Takeaways
✓ DFS with time tracking effectively models rot spread timing in a grid.
This insight is hard to see from code alone because the time updates and pruning conditions are subtle.
✓ The maximum time recorded corresponds to the longest path rot must travel to reach all fresh oranges.
Visualizing the time grid clarifies how rot propagation depends on grid layout and adjacency.
✓ DFS backtracking ensures all neighbors are explored and rot times updated only if earlier than previous.
Seeing the recursive calls and returns step-by-step reveals why some paths update times and others don't.
Practice
(1/5)
1. You need to find the cheapest route from a source city to a destination city with at most K stops in between. The graph edges represent flights with costs, and you want the minimum total cost under the stop constraint. Which algorithmic approach guarantees finding the optimal solution efficiently?
easy
A. Dynamic programming using a 2D table indexed by stops and cities without priority queue.
B. A simple DFS with backtracking exploring all paths up to K stops.
C. A greedy algorithm that always picks the cheapest next flight regardless of stops.
D. Dijkstra's algorithm modified to track the number of stops along with cost.
Solution
Step 1: Understand the problem constraints
The problem requires the cheapest path with at most K stops, so we must track both cost and stops.
Step 2: Identify suitable algorithm
Dijkstra's algorithm can be adapted to track stops as a state parameter, ensuring paths exceeding K stops are not considered, guaranteeing optimality efficiently.
Final Answer:
Option D -> Option D
Quick Check:
Modified Dijkstra tracks stops and cost simultaneously [OK]
Hint: Track stops in Dijkstra to enforce constraints [OK]
Common Mistakes:
Assuming greedy cheapest next flight always works
Using DFS without pruning leads to TLE
Ignoring stops count in shortest path
2. You are given a list of edges representing an undirected graph with n nodes labeled from 0 to n-1. You need to determine if the graph forms a valid tree. Which algorithmic approach guarantees an optimal solution to check both connectivity and absence of cycles efficiently?
easy
A. Use a greedy algorithm that adds edges one by one and checks if the graph remains connected.
B. Apply dynamic programming to count the number of ways to reach each node and detect cycles.
C. Sort edges and use binary search to detect cycles and connectivity.
D. Perform a BFS or DFS traversal to detect cycles and verify that all nodes are connected.
Solution
Step 1: Understand problem requirements
The problem requires checking if the graph is connected and contains no cycles, which defines a tree.
Step 2: Identify suitable algorithm
BFS or DFS traversal can detect cycles by tracking visited nodes and parent relationships, and also verify connectivity by ensuring all nodes are visited.
Final Answer:
Option D -> Option D
Quick Check:
BFS/DFS simultaneously checks connectivity and cycles efficiently [OK]
3. What is the time complexity of the optimal Dijkstra-based algorithm for finding the cheapest flight within K stops, given n cities and E flights? Assume the graph is represented as adjacency lists and a min-heap is used.
A) O(n^2) because we may visit all nodes multiple times.
B) O(E log E) because each edge can be pushed into the heap once.
C) O(n log n) because Dijkstra runs in that time on sparse graphs.
D) O(K * E) because we relax edges up to K times.
medium
A. O(n^2) because we may visit all nodes multiple times.
B. O(K * E) because we relax edges up to K times.
C. O(E log E) because each edge can be pushed into the heap once.
D. O(n log n) because Dijkstra runs in that time on sparse graphs.
Solution
Step 1: Identify heap operations
Each edge can be pushed into the heap at most once per stops count, but stops ≤ K limits pushes. The heap size is bounded by O(E).
Step 2: Analyze complexity
Each push/pop operation costs O(log E), so total time is O(E log E). The stops constraint prevents exponential blowup.
Hint: Heap operations on edges dominate complexity [OK]
Common Mistakes:
Assuming O(n^2) from classic Dijkstra on adjacency matrix
Ignoring that stops limit expansions
Confusing Bellman-Ford complexity with this approach
4. Examine the following buggy code for the Keys and Rooms problem:
def canVisitAllRooms(rooms):
n = len(rooms)
visited = [false] * n
count = 0
def dfs(room):
if visited[room]:
return
for key in rooms[room]:
dfs(key)
visited[room] = true
nonlocal count
count += 1
dfs(0)
return count == n
What is the bug in this code?
medium
A. Visited marking happens after recursive calls, causing repeated visits
B. Count is incremented before marking visited, causing off-by-one errors
C. Visited array is initialized incorrectly with zeros instead of false
D. DFS is called on room 0 instead of all rooms
Solution
Step 1: Analyze visited marking position
Visited is marked after recursive calls, so the same room can be visited multiple times during recursion.
Step 2: Identify consequence of bug
This causes repeated visits and potentially infinite recursion or incorrect count.
Final Answer:
Option A -> Option A
Quick Check:
Visited must be marked before recursion to avoid revisits [OK]
Hint: Mark visited before recursion to prevent repeated visits [OK]
Common Mistakes:
Marking visited after recursion leads to infinite loops
Incrementing count before visited marking
Incorrect initialization of visited array
5. The following BFS code attempts to find the max area of an island in a grid. Identify the line that contains a subtle bug that can cause incorrect area calculation or runtime errors.
from collections import deque
def maxAreaOfIsland(grid):
rows, cols = len(grid), len(grid[0])
visited = set()
max_area = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == 1 and (r, c) not in visited:
queue = deque([(r, c)])
area = 0
while queue:
row, col = queue.popleft()
# Bug: Missing visited check here
visited.add((row, col))
area += 1
for nr, nc in [(row+1,col),(row-1,col),(row,col+1),(row,col-1)]:
if grid[nr][nc] == 1 and (nr, nc) not in visited:
queue.append((nr, nc))
max_area = max(max_area, area)
return max_area
medium
A. Line that adds neighbors to the queue without boundary checks
B. Line that adds (row, col) to visited without checking if already visited
C. Line that initializes the queue with the starting cell
D. Line that updates max_area after BFS completes
Solution
Step 1: Check neighbor addition line
The code adds neighbors without verifying if nr, nc are within grid boundaries, risking index out of range errors.
Step 2: Confirm other lines are correct
Visited check before adding to queue is present, queue initialization and max_area update are correct.
Final Answer:
Option A -> Option A
Quick Check:
Boundary checks missing cause runtime errors [OK]
Hint: Always check grid boundaries before accessing neighbors [OK]
Common Mistakes:
Forgetting boundary checks
Not checking visited before adding to queue
Adding visited check after popping queue
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.