2258. Escape the Spreading Fire
Problem Description
You are given a 2D grid representing a field where:
0represents grass cells you can walk on1represents fire cells that will spread2represents walls that block both you and fire
You start at the top-left corner (0, 0) and need to reach the safehouse at the bottom-right corner (m-1, n-1).
The game mechanics work as follows:
- Initially, you can choose to stay at your starting position for some number of minutes
- Each minute you stay, the fire spreads to all adjacent grass cells (not through walls)
- Once you start moving, you can move to an adjacent grass cell each minute
- After each of your moves, the fire spreads again to adjacent grass cells
- You cannot move through fire or walls
The goal is to find the maximum number of minutes you can wait at the starting position before beginning your journey, while still being able to safely reach the safehouse.
Return values:
- Return the maximum number of minutes you can wait if it's possible to reach the safehouse
- Return
-1if it's impossible to reach the safehouse at all - Return
10^9if you can wait indefinitely and still reach the safehouse
Note that reaching the safehouse counts as safe even if fire spreads to it immediately after you arrive there.
Adjacent cells are those directly north, east, south, or west (sharing a side).
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 problem involves a 2D grid where we need to find paths. Each cell can be considered a node, and adjacent cells form edges. We need to navigate from one position to another while avoiding obstacles (walls and fire).
Is it a tree?
- No: The grid structure is not a tree. It's a graph where cells can have multiple paths between them and may contain cycles.
Is the problem related to directed acyclic graphs?
- No: The grid allows movement in all four directions (when not blocked), so it's not a DAG and can have cycles.
Is the problem related to shortest paths?
- Yes: We need to find if a path exists from start
(0,0)to end(m-1,n-1)while considering the spreading fire. The problem involves finding the optimal path timing - specifically, we're looking for the path that allows maximum waiting time at the start.
Is the graph Weighted?
- No: All movements between adjacent cells take the same amount of time (1 minute). The graph is unweighted.
BFS
- Yes: Since we have an unweighted graph and need to explore paths level by level (simulating minute-by-minute progression), BFS is the appropriate choice.
Conclusion: The flowchart suggests using BFS for this problem. This makes sense because:
- We need to simulate the fire spreading level by level (minute by minute)
- We need to find if a path exists from start to end position
- The graph is unweighted (each move takes 1 minute)
- BFS naturally handles the simultaneous exploration needed for both fire spreading and player movement
The solution uses BFS twice in each check:
- First BFS simulates fire spreading for the initial waiting time
- Second BFS simulates the player's movement while simultaneously spreading fire after each move
Intuition
The key insight is recognizing that this problem has a monotonic property: if we can safely reach the safehouse after waiting t minutes, then we can also reach it after waiting any time less than t. Why? Because less waiting time means the fire has spread less, giving us more safe paths to work with.
This monotonic property immediately suggests binary search on the answer. We can search for the maximum waiting time between 0 and m × n (the total number of cells, which is an upper bound since fire can't spread more than this).
For each candidate waiting time mid in our binary search, we need to check if it's possible to reach the safehouse. This check involves two phases:
-
Fire spreading phase: First, simulate the fire spreading for
midminutes while we wait at the starting position. We use BFS to spread fire level by level, where each level represents one minute. -
Escape phase: After waiting, we start moving. Here's where it gets tricky - we need to simulate both our movement AND the fire continuing to spread simultaneously. We move one step per minute, and after each round of our movement, the fire spreads one more level.
The simulation works like this:
- Use BFS for the player's movement, exploring all reachable cells at distance
dbefore moving to distanced+1 - After processing each distance level (one minute of player movement), spread the fire one more level
- If we reach
(m-1, n-1)before getting caught by fire, this waiting time works - If the fire blocks all paths or catches us, this waiting time doesn't work
Special cases to consider:
- If we can wait
m × nminutes and still escape, we can wait indefinitely (return10^9) - If we can't escape even without waiting (when
t = 0), return-1
The beauty of this approach is that binary search reduces our search space from O(m × n) possible waiting times to just O(log(m × n)) checks, and each check is done efficiently using BFS in O(m × n) time.
Learn more about Breadth-First Search and Binary Search patterns.
Solution Approach
The solution implements binary search combined with BFS simulation. Let's break down the implementation:
Binary Search Using first_true_index Template
This problem finds the maximum waiting time where escape is possible. We use the first_true_index template by inverting the condition: instead of finding where escape IS possible, we find where escape is NOT possible.
Setup:
- Search space:
0tom * n(maximum possible waiting time) - Inverted condition:
cannot_escape(mid)returns true when we cannot escape withmidminutes wait - Pattern:
false, false, ..., true, true(can escape, can escape, ..., cannot escape, cannot escape) - Answer:
first_true_index - 1gives the last time we CAN escape
left, right = 0, rows * cols first_true_index = rows * cols + 1 # Default: all times allow escape while left <= right: mid = (left + right) // 2 if not can_escape_with_delay(mid): # Cannot escape = condition is true first_true_index = mid right = mid - 1 else: left = mid + 1 # Maximum wait time = first_true_index - 1 return 10**9 if first_true_index > rows * cols else first_true_index - 1
The Check Function
The check(t) function determines if we can reach the safehouse after waiting t minutes. It consists of two main phases:
Phase 1: Initial Fire Spreading
# Initialize fire grid and queue with all initial fire positions
fire = [[False] * n for _ in range(m)]
q1 = deque()
for i, row in enumerate(grid):
for j, x in enumerate(row):
if x == 1: # Found fire
fire[i][j] = True
q1.append((i, j))
# Spread fire for t minutes while we wait
while t and q1:
q1 = spread(q1)
t -= 1
Phase 2: Simultaneous Movement and Fire Spreading
# Check if starting position is on fire
if fire[0][0]:
return False
# BFS for player movement
q2 = deque([(0, 0)])
vis = [[False] * n for _ in range(m)]
vis[0][0] = True
while q2:
# Process all cells at current distance (one minute of movement)
for _ in range(len(q2)):
i, j = q2.popleft()
if fire[i][j]: # Skip if current position caught fire
continue
# Try all 4 directions
for a, b in pairwise(dirs): # dirs = (-1, 0, 1, 0, -1)
x, y = i + a, j + b
if valid_move(x, y): # Check bounds, not visited, not on fire, is grass
if x == m - 1 and y == n - 1: # Reached safehouse!
return True
vis[x][y] = True
q2.append((x, y))
# After each round of movement, fire spreads one more level
q1 = spread(q1)
The Spread Function
The spread function handles one minute of fire spreading using BFS:
def spread(q: Deque[int]) -> Deque[int]:
nq = deque() # New queue for next level
while q:
i, j = q.popleft()
for a, b in pairwise(dirs): # Check all 4 directions
x, y = i + a, j + b
if 0 <= x < m and 0 <= y < n and not fire[x][y] and grid[x][y] == 0:
fire[x][y] = True # Mark as on fire
nq.append((x, y)) # Add to next level
return nq
Direction Handling
The solution uses a clever pattern for iterating through 4 directions:
dirs = (-1, 0, 1, 0, -1)pairwise(dirs)generates pairs:(-1, 0),(0, 1),(1, 0),(0, -1)- These represent movements: up, right, down, left
Final Result
After binary search completes:
- If
l == m * n: We can wait for the maximum time, return10^9 - Otherwise: Return
las the maximum waiting time
The time complexity is O(m * n * log(m * n)) where each binary search iteration runs a BFS simulation in O(m * n) time.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Consider a 3×3 grid:
0 0 0 0 1 0 0 0 0
Where we start at (0,0) and need to reach (2,2). There's fire at (1,1).
Step 1: Binary Search Setup
- Initialize:
l = -1,r = 3 × 3 = 9 - We'll binary search for the maximum waiting time
Step 2: First Binary Search Iteration
mid = (l + r + 1) / 2 = (-1 + 9 + 1) / 2 = 4- Check if we can escape after waiting 4 minutes
Checking wait time = 4:
Phase 1: Fire spreads for 4 minutes while we wait
- Minute 0: Fire at (1,1)
- Minute 1: Fire spreads to (0,1), (1,0), (1,2), (2,1)
0 1 0 1 1 1 0 1 0 - Minute 2: Fire spreads to (0,0), (0,2), (2,0), (2,2)
1 1 1 1 1 1 1 1 1 - Minutes 3-4: All cells already on fire
Phase 2: Try to escape
- Starting position (0,0) is on fire → Cannot escape!
check(4)returns False- Update:
r = mid - 1 = 3
Step 3: Second Binary Search Iteration
mid = (-1 + 3 + 1) / 2 = 1- Check if we can escape after waiting 1 minute
Checking wait time = 1:
Phase 1: Fire spreads for 1 minute
- Initial: Fire at (1,1)
- After 1 minute: Fire at (1,1), (0,1), (1,0), (1,2), (2,1)
0 1 0 1 1 1 0 1 0
Phase 2: Escape attempt
-
Start at (0,0) - not on fire ✓
-
Our movement minute by minute:
Minute 1 (our first move):
- From (0,0), we can move to (1,0) or (0,1)
- But both are on fire! Cannot move anywhere
check(1)returns False- Update:
r = mid - 1 = 0
Step 4: Third Binary Search Iteration
mid = (-1 + 0 + 1) / 2 = 0- Check if we can escape without waiting
Checking wait time = 0:
Phase 1: No waiting (skip)
Phase 2: Immediate escape
-
Fire initially only at (1,1)
-
Our path simulation:
Minute 1:
- Player moves: (0,0) → can reach (1,0) or (0,1)
- Add both to queue
- Fire spreads: (1,1) → (0,1), (1,0), (1,2), (2,1)
Minute 2:
- Player moves from (0,1): blocked by fire
- Player moves from (1,0): blocked by fire
- Both positions caught by fire after minute 1
- No valid moves remain
-
check(0)returns False -
Update:
r = mid - 1 = -1
Step 5: Binary Search Completes
l = -1,r = -1, sol == r- Final answer:
l = -1 - Return -1 (impossible to reach safehouse)
Alternative Scenario: If the grid had been:
0 0 0 0 0 1 0 0 0
With fire at (1,2) instead, we could find a path:
- Without waiting: (0,0) → (0,1) → (0,2) → (1,2) would be blocked
- But: (0,0) → (1,0) → (2,0) → (2,1) → (2,2) works!
- Binary search would find we can wait 0 minutes and still escape
- If we could wait longer and still escape, binary search would find that maximum
This example shows how the algorithm combines binary search to find the optimal waiting time with BFS simulation to check if escape is possible for each candidate time.
Solution Implementation
1class Solution:
2 def maximumMinutes(self, grid: List[List[int]]) -> int:
3 def spread_fire(fire_queue: Deque[Tuple[int, int]]) -> Deque[Tuple[int, int]]:
4 """
5 Spread fire to adjacent cells for one time unit.
6 Returns a new queue with newly burning cells.
7 """
8 new_queue = deque()
9 while fire_queue:
10 row, col = fire_queue.popleft()
11 # Check all 4 adjacent cells (up, right, down, left)
12 for i in range(4):
13 new_row = row + directions[i]
14 new_col = col + directions[i + 1]
15 # Check if the new position is valid and can catch fire
16 if (0 <= new_row < rows and
17 0 <= new_col < cols and
18 not is_on_fire[new_row][new_col] and
19 grid[new_row][new_col] == 0):
20 is_on_fire[new_row][new_col] = True
21 new_queue.append((new_row, new_col))
22 return new_queue
23
24 def can_escape_with_delay(wait_time: int) -> bool:
25 """
26 Check if we can reach the safehouse starting after 'wait_time' minutes.
27 """
28 # Reset fire grid
29 for row in range(rows):
30 for col in range(cols):
31 is_on_fire[row][col] = False
32
33 # Initialize fire starting positions
34 fire_queue = deque()
35 for row in range(rows):
36 for col in range(cols):
37 if grid[row][col] == 1:
38 is_on_fire[row][col] = True
39 fire_queue.append((row, col))
40
41 # Let fire spread for 'wait_time' minutes before we start moving
42 remaining_time = wait_time
43 while remaining_time > 0 and fire_queue:
44 fire_queue = spread_fire(fire_queue)
45 remaining_time -= 1
46
47 # If starting position is on fire, we can't escape
48 if is_on_fire[0][0]:
49 return False
50
51 # BFS to find if we can reach the safehouse
52 person_queue = deque([(0, 0)])
53 visited = [[False] * cols for _ in range(rows)]
54 visited[0][0] = True
55
56 while person_queue:
57 # Process all positions reachable in current minute
58 current_level_size = len(person_queue)
59 for _ in range(current_level_size):
60 row, col = person_queue.popleft()
61 # Skip if current position is on fire
62 if is_on_fire[row][col]:
63 continue
64
65 # Try moving to adjacent cells
66 for i in range(4):
67 new_row = row + directions[i]
68 new_col = col + directions[i + 1]
69
70 if (0 <= new_row < rows and
71 0 <= new_col < cols and
72 not visited[new_row][new_col] and
73 not is_on_fire[new_row][new_col] and
74 grid[new_row][new_col] == 0):
75 # Check if we reached the safehouse
76 if new_row == rows - 1 and new_col == cols - 1:
77 return True
78 visited[new_row][new_col] = True
79 person_queue.append((new_row, new_col))
80
81 # Fire spreads after each minute of movement
82 fire_queue = spread_fire(fire_queue)
83
84 return False
85
86 # Initialize grid dimensions
87 rows, cols = len(grid), len(grid[0])
88
89 # Direction vectors for adjacent cells: up, right, down, left
90 directions = [-1, 0, 1, 0, -1]
91
92 # Track which cells are on fire
93 is_on_fire = [[False] * cols for _ in range(rows)]
94
95 # Binary search using first_true_index template
96 # We invert the condition: find first time where escape FAILS
97 left, right = 0, rows * cols
98 first_true_index = rows * cols + 1 # Default: all times allow escape
99
100 while left <= right:
101 mid = (left + right) // 2
102 if not can_escape_with_delay(mid): # Cannot escape = condition is true
103 first_true_index = mid
104 right = mid - 1
105 else:
106 left = mid + 1
107
108 # Maximum wait time = first_true_index - 1
109 # If first_true_index > rows * cols, all times allow escape (return 10^9)
110 # Otherwise, return first_true_index - 1 (the last time escape is possible)
111 if first_true_index > rows * cols:
112 return int(1e9)
113 return first_true_index - 1
1141class Solution {
2 // Grid dimensions and data
3 private int[][] grid;
4 private boolean[][] fireSpread; // Tracks cells on fire
5 private boolean[][] visited; // Tracks visited cells by person
6 private final int[] directions = {-1, 0, 1, 0, -1}; // Direction vectors for 4-directional movement
7 private int rows;
8 private int cols;
9
10 /**
11 * Finds the maximum number of minutes to wait at start position before escaping.
12 * Uses binary search to find the maximum safe waiting time.
13 *
14 * @param grid 2D array where 0=grass, 1=fire, 2=wall
15 * @return Maximum minutes to wait, or 1000000000 if can wait indefinitely
16 */
17 public int maximumMinutes(int[][] grid) {
18 rows = grid.length;
19 cols = grid[0].length;
20 this.grid = grid;
21 fireSpread = new boolean[rows][cols];
22 visited = new boolean[rows][cols];
23
24 // Binary search using first_true_index template
25 // Inverted condition: find first time where escape FAILS
26 int left = 0;
27 int right = rows * cols;
28 int firstTrueIndex = rows * cols + 1; // Default: all times allow escape
29
30 while (left <= right) {
31 int mid = left + (right - left) / 2;
32 if (!check(mid)) { // Cannot escape = condition is true
33 firstTrueIndex = mid;
34 right = mid - 1;
35 } else {
36 left = mid + 1;
37 }
38 }
39
40 // Maximum wait time = firstTrueIndex - 1
41 // If firstTrueIndex > rows * cols, all times allow escape
42 return firstTrueIndex > rows * cols ? 1000000000 : firstTrueIndex - 1;
43 }
44
45 /**
46 * Checks if person can reach the safehouse after waiting t minutes.
47 * Simulates fire spread for t minutes, then person movement with simultaneous fire spread.
48 *
49 * @param waitTime Number of minutes to wait before starting
50 * @return true if person can reach bottom-right corner, false otherwise
51 */
52 private boolean check(int waitTime) {
53 // Reset fire and visited arrays
54 for (int i = 0; i < rows; ++i) {
55 Arrays.fill(fireSpread[i], false);
56 Arrays.fill(visited[i], false);
57 }
58
59 // Initialize fire queue with all initial fire positions
60 Deque<int[]> fireQueue = new ArrayDeque<>();
61 for (int i = 0; i < rows; ++i) {
62 for (int j = 0; j < cols; ++j) {
63 if (grid[i][j] == 1) {
64 fireQueue.offer(new int[] {i, j});
65 fireSpread[i][j] = true;
66 }
67 }
68 }
69
70 // Spread fire for waitTime minutes before person starts moving
71 for (; waitTime > 0 && !fireQueue.isEmpty(); --waitTime) {
72 fireQueue = spread(fireQueue);
73 }
74
75 // If starting position is on fire, cannot escape
76 if (fireSpread[0][0]) {
77 return false;
78 }
79
80 // Initialize person's BFS from top-left corner
81 Deque<int[]> personQueue = new ArrayDeque<>();
82 personQueue.offer(new int[] {0, 0});
83 visited[0][0] = true;
84
85 // Simultaneous BFS: person moves, then fire spreads
86 for (; !personQueue.isEmpty(); fireQueue = spread(fireQueue)) {
87 // Process all positions person can reach in current minute
88 for (int size = personQueue.size(); size > 0; --size) {
89 int[] currentPos = personQueue.poll();
90
91 // Skip if current position is on fire
92 if (fireSpread[currentPos[0]][currentPos[1]]) {
93 continue;
94 }
95
96 // Try moving in all 4 directions
97 for (int k = 0; k < 4; ++k) {
98 int nextRow = currentPos[0] + directions[k];
99 int nextCol = currentPos[1] + directions[k + 1];
100
101 // Check if next position is valid and safe
102 if (nextRow >= 0 && nextRow < rows && nextCol >= 0 && nextCol < cols
103 && !fireSpread[nextRow][nextCol] && !visited[nextRow][nextCol]
104 && grid[nextRow][nextCol] == 0) {
105
106 // Check if reached destination (bottom-right corner)
107 if (nextRow == rows - 1 && nextCol == cols - 1) {
108 return true;
109 }
110
111 visited[nextRow][nextCol] = true;
112 personQueue.offer(new int[] {nextRow, nextCol});
113 }
114 }
115 }
116 }
117
118 return false;
119 }
120
121 /**
122 * Spreads fire to adjacent grass cells for one minute.
123 *
124 * @param currentFireQueue Current positions of fire
125 * @return New queue with fire positions after spreading
126 */
127 private Deque<int[]> spread(Deque<int[]> currentFireQueue) {
128 Deque<int[]> nextFireQueue = new ArrayDeque<>();
129
130 while (!currentFireQueue.isEmpty()) {
131 int[] firePos = currentFireQueue.poll();
132
133 // Spread fire in all 4 directions
134 for (int k = 0; k < 4; ++k) {
135 int nextRow = firePos[0] + directions[k];
136 int nextCol = firePos[1] + directions[k + 1];
137
138 // Check if fire can spread to this cell (must be grass and not already on fire)
139 if (nextRow >= 0 && nextRow < rows && nextCol >= 0 && nextCol < cols
140 && !fireSpread[nextRow][nextCol] && grid[nextRow][nextCol] == 0) {
141
142 fireSpread[nextRow][nextCol] = true;
143 nextFireQueue.offer(new int[] {nextRow, nextCol});
144 }
145 }
146 }
147
148 return nextFireQueue;
149 }
150}
1511class Solution {
2public:
3 int maximumMinutes(vector<vector<int>>& grid) {
4 int rows = grid.size(), cols = grid[0].size();
5
6 // Direction vectors for 4-directional movement (up, right, down, left)
7 int directions[5] = {-1, 0, 1, 0, -1};
8
9 // Lambda function to spread fire to adjacent cells
10 auto spreadFire = [&](queue<pair<int, int>>& fireQueue, vector<vector<bool>>& fireGrid) {
11 queue<pair<int, int>> nextQueue;
12
13 while (!fireQueue.empty()) {
14 auto [row, col] = fireQueue.front();
15 fireQueue.pop();
16
17 // Try spreading fire in all 4 directions
18 for (int k = 0; k < 4; ++k) {
19 int newRow = row + directions[k];
20 int newCol = col + directions[k + 1];
21
22 // Check if new position is valid and can catch fire
23 if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols &&
24 !fireGrid[newRow][newCol] && grid[newRow][newCol] == 0) {
25 fireGrid[newRow][newCol] = true;
26 nextQueue.emplace(newRow, newCol);
27 }
28 }
29 }
30 return nextQueue;
31 };
32
33 // Lambda function to check if we can escape with 'waitTime' minutes delay
34 auto canEscape = [&](int waitTime) {
35 vector<vector<bool>> visited(rows, vector<bool>(cols, false));
36 vector<vector<bool>> onFire(rows, vector<bool>(cols, false));
37
38 // Initialize fire positions from grid
39 queue<pair<int, int>> fireQueue;
40 for (int i = 0; i < rows; ++i) {
41 for (int j = 0; j < cols; ++j) {
42 if (grid[i][j] == 1) {
43 fireQueue.emplace(i, j);
44 onFire[i][j] = true;
45 }
46 }
47 }
48
49 // Spread fire for 'waitTime' minutes before we start moving
50 for (int t = 0; t < waitTime && !fireQueue.empty(); ++t) {
51 fireQueue = spreadFire(fireQueue, onFire);
52 }
53
54 // If starting position is on fire, we can't escape
55 if (onFire[0][0]) {
56 return false;
57 }
58
59 // BFS to find path from (0,0) to (rows-1, cols-1)
60 queue<pair<int, int>> personQueue;
61 personQueue.emplace(0, 0);
62 visited[0][0] = true;
63
64 // Simulate person movement and fire spread simultaneously
65 while (!personQueue.empty()) {
66 // Process all positions reachable in current minute
67 int queueSize = personQueue.size();
68 for (int d = 0; d < queueSize; ++d) {
69 auto [row, col] = personQueue.front();
70 personQueue.pop();
71
72 // Skip if current position is on fire
73 if (onFire[row][col]) {
74 continue;
75 }
76
77 // Try moving in all 4 directions
78 for (int k = 0; k < 4; ++k) {
79 int newRow = row + directions[k];
80 int newCol = col + directions[k + 1];
81
82 // Check if new position is valid and safe
83 if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols &&
84 !visited[newRow][newCol] && !onFire[newRow][newCol] &&
85 grid[newRow][newCol] == 0) {
86
87 // Check if we reached the destination
88 if (newRow == rows - 1 && newCol == cols - 1) {
89 return true;
90 }
91
92 visited[newRow][newCol] = true;
93 personQueue.emplace(newRow, newCol);
94 }
95 }
96 }
97
98 // Spread fire after person moves
99 fireQueue = spreadFire(fireQueue, onFire);
100 }
101
102 return false;
103 };
104
105 // Binary search using first_true_index template
106 // Inverted condition: find first time where escape FAILS
107 int left = 0, right = rows * cols;
108 int firstTrueIndex = rows * cols + 1; // Default: all times allow escape
109
110 while (left <= right) {
111 int mid = left + (right - left) / 2;
112 if (!canEscape(mid)) { // Cannot escape = condition is true
113 firstTrueIndex = mid;
114 right = mid - 1;
115 } else {
116 left = mid + 1;
117 }
118 }
119
120 // Maximum wait time = firstTrueIndex - 1
121 // If firstTrueIndex > rows * cols, all times allow escape
122 return firstTrueIndex > rows * cols ? 1000000000 : firstTrueIndex - 1;
123 }
124};
1251/**
2 * Finds the maximum number of minutes you can wait before starting to escape from fire
3 * @param grid - 2D grid where 0 is empty, 1 is fire, 2 is wall
4 * @returns Maximum minutes to wait, or 1e9 if you can wait indefinitely
5 */
6function maximumMinutes(grid: number[][]): number {
7 const rows = grid.length;
8 const cols = grid[0].length;
9
10 // Track fire spread positions
11 const fireSpread = Array.from({ length: rows }, () =>
12 Array.from({ length: cols }, () => false)
13 );
14
15 // Track visited positions for person's movement
16 const visited = Array.from({ length: rows }, () =>
17 Array.from({ length: cols }, () => false)
18 );
19
20 // Direction vectors for 4-directional movement (up, right, down, left)
21 const directions: number[] = [-1, 0, 1, 0, -1];
22
23 // Binary search using first_true_index template
24 // Inverted condition: find first time where escape FAILS
25 let left = 0;
26 let right = rows * cols;
27 let firstTrueIndex = rows * cols + 1; // Default: all times allow escape
28
29 /**
30 * Spreads fire to adjacent cells for one time unit
31 * @param fireQueue - Current positions of fire fronts
32 * @returns New fire front positions after spreading
33 */
34 const spreadFire = (fireQueue: number[][]): number[][] => {
35 const nextQueue: number[][] = [];
36
37 while (fireQueue.length > 0) {
38 const [row, col] = fireQueue.shift()!;
39
40 // Check all 4 directions
41 for (let dir = 0; dir < 4; dir++) {
42 const newRow = row + directions[dir];
43 const newCol = col + directions[dir + 1];
44
45 // Spread fire if within bounds, not already on fire, and is empty cell
46 if (newRow >= 0 && newRow < rows &&
47 newCol >= 0 && newCol < cols &&
48 !fireSpread[newRow][newCol] &&
49 grid[newRow][newCol] === 0) {
50
51 fireSpread[newRow][newCol] = true;
52 nextQueue.push([newRow, newCol]);
53 }
54 }
55 }
56
57 return nextQueue;
58 };
59
60 /**
61 * Checks if person can reach bottom-right corner after waiting waitTime minutes
62 * @param waitTime - Number of minutes to wait before starting
63 * @returns true if person can reach destination, false otherwise
64 */
65 const canReachDestination = (waitTime: number): boolean => {
66 // Reset fire and visited arrays
67 for (let i = 0; i < rows; i++) {
68 fireSpread[i].fill(false);
69 visited[i].fill(false);
70 }
71
72 // Initialize fire positions from grid
73 let fireQueue: number[][] = [];
74 for (let i = 0; i < rows; i++) {
75 for (let j = 0; j < cols; j++) {
76 if (grid[i][j] === 1) {
77 fireQueue.push([i, j]);
78 fireSpread[i][j] = true;
79 }
80 }
81 }
82
83 // Spread fire for waitTime minutes
84 for (let time = waitTime; time > 0 && fireQueue.length > 0; time--) {
85 fireQueue = spreadFire(fireQueue);
86 }
87
88 // If starting position is on fire, cannot escape
89 if (fireSpread[0][0]) {
90 return false;
91 }
92
93 // BFS for person's movement starting from top-left
94 const personQueue: number[][] = [[0, 0]];
95 visited[0][0] = true;
96
97 // Simultaneous BFS: person moves and fire spreads each minute
98 while (personQueue.length > 0) {
99 // Process all positions person can reach in current minute
100 const currentLevelSize = personQueue.length;
101
102 for (let level = currentLevelSize; level > 0; level--) {
103 const [currentRow, currentCol] = personQueue.shift()!;
104
105 // Skip if current position is on fire
106 if (fireSpread[currentRow][currentCol]) {
107 continue;
108 }
109
110 // Try moving in all 4 directions
111 for (let dir = 0; dir < 4; dir++) {
112 const nextRow = currentRow + directions[dir];
113 const nextCol = currentCol + directions[dir + 1];
114
115 // Check if next position is valid and safe
116 if (nextRow >= 0 && nextRow < rows &&
117 nextCol >= 0 && nextCol < cols &&
118 !visited[nextRow][nextCol] &&
119 !fireSpread[nextRow][nextCol] &&
120 grid[nextRow][nextCol] === 0) {
121
122 // Check if reached destination
123 if (nextRow === rows - 1 && nextCol === cols - 1) {
124 return true;
125 }
126
127 visited[nextRow][nextCol] = true;
128 personQueue.push([nextRow, nextCol]);
129 }
130 }
131 }
132
133 // Spread fire after person's movement
134 fireQueue = spreadFire(fireQueue);
135 }
136
137 return false;
138 };
139
140 // Binary search for maximum waiting time
141 while (left <= right) {
142 const mid = Math.floor((left + right) / 2);
143
144 if (!canReachDestination(mid)) { // Cannot escape = condition is true
145 firstTrueIndex = mid;
146 right = mid - 1;
147 } else {
148 left = mid + 1;
149 }
150 }
151
152 // Maximum wait time = firstTrueIndex - 1
153 // If firstTrueIndex > rows * cols, all times allow escape
154 return firstTrueIndex > rows * cols ? 1e9 : firstTrueIndex - 1;
155}
156Time and Space Complexity
Time Complexity: O(m × n × log(m × n))
The algorithm uses binary search on the range [-1, m × n], which requires O(log(m × n)) iterations. For each binary search iteration, the check function is called.
Within the check function:
- Initial fire spreading for
tminutes: Each spread operation processes at mostO(m × n)cells, and we do this at mostttimes wheret ≤ m × n, givingO(m × n)time. - BFS for the person's movement: The person's BFS visits each cell at most once, taking
O(m × n)time. - Fire spreading during person's movement: The fire also spreads while the person moves, visiting each cell at most once throughout the entire process, contributing
O(m × n)time. - Grid initialization and copying:
O(m × n)for initializing thefireandvisarrays.
Therefore, each check function call takes O(m × n) time, and with O(log(m × n)) binary search iterations, the total time complexity is O(m × n × log(m × n)).
Space Complexity: O(m × n)
The algorithm uses:
firearray:O(m × n)space to track cells on firevisarray:O(m × n)space to track visited cells during BFS- Two deques
q1andq2: In the worst case, these can containO(m × n)elements - Recursive stack space: The algorithm is iterative, so no additional stack space is needed
The dominant space usage is O(m × n) from the 2D arrays and queues.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template
This problem finds the maximum waiting time where escape is possible. The correct approach inverts the condition to use the first_true_index template.
Wrong approach:
left, right = -1, rows * cols while left < right: mid = (left + right + 1) >> 1 if can_escape(mid): left = mid else: right = mid - 1 return left
Correct approach (first_true_index template):
left, right = 0, rows * cols first_true_index = rows * cols + 1 # Default: all times allow escape while left <= right: mid = (left + right) // 2 if not can_escape(mid): # Inverted: find first time where escape FAILS first_true_index = mid right = mid - 1 else: left = mid + 1 return 10**9 if first_true_index > rows * cols else first_true_index - 1
The key insight is to invert the condition: instead of finding where we CAN escape, we find the first time where we CANNOT escape, then return first_true_index - 1.
2. Incorrect Handling of the Safehouse Arrival Condition
One of the most common mistakes is incorrectly handling when the player reaches the safehouse at the same time fire spreads to it. The problem states that "reaching the safehouse counts as safe even if fire spreads to it immediately after you arrive there."
Pitfall Code:
# WRONG: Checking if safehouse is on fire before attempting to reach it if is_on_fire[rows - 1][cols - 1]: return False # This prevents us from reaching safehouse even if we arrive at the same time # Processing movement... if new_row == rows - 1 and new_col == cols - 1: return True
Correct Approach:
# Check for safehouse DURING movement processing, not before
for i in range(4):
new_row = row + directions[i]
new_col = col + directions[i + 1]
if (0 <= new_row < rows and
0 <= new_col < cols and
not visited[new_row][new_col] and
not is_on_fire[new_row][new_col] and # Fire hasn't reached here YET
grid[new_row][new_col] == 0):
# Check safehouse immediately after confirming we can move there
if new_row == rows - 1 and new_col == cols - 1:
return True # Safe arrival even if fire spreads here next
3. Fire Spreading Order Within BFS
Another critical pitfall is spreading fire at the wrong time relative to player movement. Fire should spread AFTER each complete round of player movements, not before or during.
Pitfall Code:
while person_queue:
# WRONG: Spreading fire before processing movements
fire_queue = spread_fire(fire_queue)
for _ in range(len(person_queue)):
row, col = person_queue.popleft()
# Process movement...
Correct Approach:
while person_queue:
current_level_size = len(person_queue)
# Process ALL movements at current distance first
for _ in range(current_level_size):
row, col = person_queue.popleft()
# Process movement...
# Fire spreads AFTER all movements in this minute complete
fire_queue = spread_fire(fire_queue)
4. Binary Search Boundary Initialization
A subtle but important pitfall is incorrectly initializing the binary search boundaries, particularly when no waiting is possible.
Pitfall Code:
# WRONG: Starting from 0 misses the case where we can't wait at all left, right = 0, rows * cols # This would return 0 when we should return -1 for impossible cases
Correct Approach:
# Start from -1 to handle the case where immediate movement is required left, right = -1, rows * cols # This allows the binary search to correctly identify: # - Return -1 when impossible (left stays at -1) # - Return 0 when we must move immediately but can escape # - Return positive values for actual waiting times
5. Resetting State Between Binary Search Iterations
Failing to properly reset the fire state between different binary search attempts can lead to incorrect results.
Pitfall Code:
# WRONG: Using a global fire state that persists between checks
is_on_fire = [[False] * cols for _ in range(rows)] # Defined once
def can_escape_with_delay(wait_time):
# Forgetting to reset is_on_fire
# Previous fire positions contaminate new simulation
Correct Approach:
def can_escape_with_delay(wait_time):
# Reset fire grid at the start of each check
for row in range(rows):
for col in range(cols):
is_on_fire[row][col] = False
# Then initialize with original fire positions
for row in range(rows):
for col in range(cols):
if grid[row][col] == 1:
is_on_fire[row][col] = True
fire_queue.append((row, col))
6. Edge Case: Starting Position Already Has Fire
Not checking if the starting position (0, 0) initially contains fire or a wall.
Solution Enhancement:
# Add this check at the beginning of the main function if grid[0][0] != 0 or grid[rows-1][cols-1] != 0: return -1 # Starting position or safehouse is not grass
Which two pointer techniques do you use to check if a string is a palindrome?
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
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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
Want a Structured Path to Master System Design Too? Don’t Miss This!