Escape the Spreading Fire
HardPrompt
You are given a 0-indexed 2D integer array grid of size m x n which represents a field. Each cell has one of three values:
0represents grass,1represents fire,2represents a wall that you and fire cannot pass through.
You are situated in the top-left cell, (0, 0), and you want to travel to the safehouse at the bottom-right cell, (m - 1, n - 1). Every minute, you may move to an adjacent grass cell. After your move, every fire cell will spread to all adjacent cells that are not walls.
Return the maximum number of minutes that you can stay in your initial position before moving while still safely reaching the safehouse. If this is impossible, return -1. If you can always reach the safehouse regardless of the minutes stayed, return 109.
Note that even if the fire spreads to the safehouse immediately after you have reached it, it will be counted as safely reaching the safehouse.
A cell is adjacent to another cell if the former is directly north, east, south, or west of the latter (i.e., their sides are touching).
Example 1:
Input: grid = [[0,2,0,0,0,0,0],[0,0,0,2,2,1,0],[0,2,0,0,1,2,0],[0,0,2,2,2,0,2],[0,0,0,0,0,0,0]]
Output: 3
Explanation: The figure above shows the scenario where you stay in the initial position for 3 minutes.
You will still be able to safely reach the safehouse.
Staying for more than 3 minutes will not allow you to safely reach the safehouse.Example 2:
Input: grid = [[0,0,0,0],[0,1,2,0],[0,2,0,0]]
Output: -1
Explanation: The figure above shows the scenario where you immediately move towards the safehouse.
Fire will spread to any cell you move towards and it is impossible to safely reach the safehouse.
Thus, -1 is returned.Example 3:
Input: grid = [[0,0,0],[2,2,0],[1,2,0]]
Output: 1000000000
Explanation: The figure above shows the initial grid.
Notice that the fire is contained by walls and you will always be able to safely reach the safehouse.
Thus, 109 is returned.
Constraints:
m == grid.lengthn == grid[i].length2 <= m, n <= 3004 <= m * n <= 2 * 104grid[i][j]is either0,1, or2.grid[0][0] == grid[m - 1][n - 1] == 0
Approaches
2 approaches with complexity analysis and trade-offs.
A straightforward but less efficient approach is to check every possible wait time W linearly. We can start from a reasonable maximum possible wait time and go down to zero. The first value of W for which we can find a safe path to the safehouse is our answer.
Algorithm
- First, pre-calculate the minimum time for fire to reach every grass cell. This is done using a multi-source BFS starting from all initial fire cells. Store these times in a
fireTimegrid. - Define a helper function
canReach(W)that takes an integerW(wait time) and returnstrueif the safehouse is reachable,falseotherwise. - The
canReach(W)function works as follows:
- It performs a BFS starting from
(0, 0)to find a path for the person. - The initial time for the person's BFS is
W. - A move to an adjacent cell
(r, c)is valid only if the person's arrival time at that cell is strictly less thanfireTime[r][c]. This ensures the person can move through the cell before it catches fire. - For the destination cell
(m-1, n-1), the arrival time can be less than or equal tofireTime[m-1][n-1].
- Check if
canReach(m*n)is true. If so, it means a path immune to fire exists, and the answer is10^9. - If not, iterate
Wfromm*n - 1down to0. The firstWfor whichcanReach(W)returnstrueis the maximum wait time. - If the loop completes without finding a solution, return
-1.
Walkthrough
The core of this approach relies on a helper function, canReach(W), which determines if it's possible to reach the safehouse given an initial wait time of W minutes.
First, we need to know when the fire reaches each cell. This can be pre-calculated using a multi-source Breadth-First Search (BFS) starting from all initial fire cells. Let's store these times in a fireTime grid. fireTime[r][c] will be the number of minutes it takes for fire to reach cell (r, c).
With the fireTime grid, the canReach(W) function can be implemented using another BFS, this time for the player's path. The player starts at (0, 0) at time W. A path is valid if for every cell (r, c) on the path, the player's arrival time is less than the fire's arrival time (fireTime[r][c]). For the final destination, the arrival times can be equal.
The main algorithm then iterates W from a maximum possible value (e.g., m * n) down to 0. For each W, it calls canReach(W). The first W that returns true is the maximum possible wait time.
Special cases to handle:
- If
canReach(m*n)is true, it implies a fire-proof path exists, so we can wait indefinitely. We return10^9. - If the loop finishes without finding a valid
W, it's impossible to reach the safehouse, so we return-1.
class Solution { int[][] dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; int m, n; public int maximumMinutes(int[][] grid) { m = grid.length; n = grid[0].length; int[][] fireTime = new int[m][n]; for (int[] row : fireTime) { java.util.Arrays.fill(row, Integer.MAX_VALUE); } calculateFireTime(grid, fireTime); if (canReach(grid, fireTime, m * n)) { return 1_000_000_000; } for (int w = m * n - 1; w >= 0; w--) { if (canReach(grid, fireTime, w)) { return w; } } return -1; } private void calculateFireTime(int[][] grid, int[][] fireTime) { java.util.Queue<int[]> q = new java.util.LinkedList<>(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { q.offer(new int[]{i, j}); fireTime[i][j] = 0; } } } int time = 0; while (!q.isEmpty()) { int size = q.size(); time++; for (int i = 0; i < size; i++) { int[] curr = q.poll(); for (int[] dir : dirs) { int nr = curr[0] + dir[0], nc = curr[1] + dir[1]; if (nr >= 0 && nr < m && nc >= 0 && nc < n && grid[nr][nc] == 0 && fireTime[nr][nc] == Integer.MAX_VALUE) { fireTime[nr][nc] = time; q.offer(new int[]{nr, nc}); } } } } } private boolean canReach(int[][] grid, int[][] fireTime, int waitTime) { if (waitTime >= fireTime[0][0]) return false; java.util.Queue<int[]> q = new java.util.LinkedList<>(); q.offer(new int[]{0, 0}); boolean[][] visited = new boolean[m][n]; visited[0][0] = true; int time = waitTime; while (!q.isEmpty()) { int size = q.size(); time++; for (int i = 0; i < size; i++) { int[] curr = q.poll(); for (int[] dir : dirs) { int nr = curr[0] + dir[0], nc = curr[1] + dir[1]; if (nr >= 0 && nr < m && nc >= 0 && nc < n && grid[nr][nc] == 0 && !visited[nr][nc]) { if (nr == m - 1 && nc == n - 1) { if (time <= fireTime[nr][nc]) return true; } if (time < fireTime[nr][nc]) { visited[nr][nc] = true; q.offer(new int[]{nr, nc}); } } } } } return false; }}Complexity
Time
O((M*N)^2), where M and N are the dimensions of the grid. The `fireTime` calculation takes O(M*N). The main loop runs up to M*N times, and each iteration calls `canReach`, which also takes O(M*N) in the worst case.
Space
O(M*N) to store the `fireTime` grid, the visited array, and the queue for BFS.
Trade-offs
Pros
Conceptually simple and easy to understand.
The logic for checking reachability is reusable in more optimal solutions.
Cons
The time complexity is high, making it likely to result in a 'Time Limit Exceeded' error on larger test cases.
A more efficient approach leverages the monotonic nature of the problem. If we can safely reach the safehouse by waiting W minutes, we can also do so by waiting any amount of time less than W. This property allows us to use binary search on the wait time W to find the maximum possible value.
Algorithm
- First, pre-calculate the
fireTimegrid using a multi-source BFS, identical to the linear scan approach. - Recognize that the problem has a monotonic property: if waiting
Wminutes is safe, waitingW-1minutes is also safe. This allows for binary search on the answer. - Set a search range for the wait time
W, for example, from0tom*n. - Initialize
low = 0,high = m*n, andans = -1. - In a loop while
low <= high:
- Calculate
mid = low + (high - low) / 2. - Use the
canReach(mid)function to check if it's possible to reach the safehouse with a wait time ofmid. - If
canReach(mid)is true,midis a possible answer. We try for a larger wait time, so we setans = midandlow = mid + 1. - If
canReach(mid)is false,midis too long. We must wait less, so we sethigh = mid - 1.
- After the loop, if
ansequals the initial upper bound (m*n), it implies we can wait indefinitely, so return10^9. - Otherwise, return
ans. This will be the maximum valid wait time, or-1if no solution exists.
Walkthrough
The overall structure is similar to the linear scan, but instead of checking every W, we use binary search to find the optimal W much faster.
The search space for W will be from 0 to a safe upper bound, like m * n.
-
Pre-computation: Just like the previous approach, we first compute the
fireTimegrid using a multi-source BFS from all fire sources. -
Binary Search: We perform a binary search on the wait time
Win the range[0, m*n]. For eachmidvalue (our candidateW), we call the samecanReach(mid)function to check if a safe path exists.- If
canReach(mid)istrue, it meansmidis a possible wait time. We try for a longer wait time by settinglow = mid + 1and storingmidas a potential answer. - If
canReach(mid)isfalse, the wait timemidis too long. We need to search for a smallerWby settinghigh = mid - 1.
- If
-
Result: After the binary search loop terminates, the stored answer will be the maximum valid wait time. We then handle the special cases: if the answer is
m*n, we return10^9; otherwise, we return the answer (which could be-1if no path was ever found).
class Solution { int[][] dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; int m, n; public int maximumMinutes(int[][] grid) { m = grid.length; n = grid[0].length; int[][] fireTime = new int[m][n]; for (int[] row : fireTime) { java.util.Arrays.fill(row, Integer.MAX_VALUE); } calculateFireTime(grid, fireTime); int low = 0, high = m * n; int ans = -1; while (low <= high) { int mid = low + (high - low) / 2; if (canReach(grid, fireTime, mid)) { ans = mid; low = mid + 1; } else { high = mid - 1; } } return ans == m * n ? 1_000_000_000 : ans; } // The calculateFireTime and canReach methods are identical to the previous approach. private void calculateFireTime(int[][] grid, int[][] fireTime) { java.util.Queue<int[]> q = new java.util.LinkedList<>(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { q.offer(new int[]{i, j}); fireTime[i][j] = 0; } } } int time = 0; while (!q.isEmpty()) { int size = q.size(); time++; for (int i = 0; i < size; i++) { int[] curr = q.poll(); for (int[] dir : dirs) { int nr = curr[0] + dir[0], nc = curr[1] + dir[1]; if (nr >= 0 && nr < m && nc >= 0 && nc < n && grid[nr][nc] == 0 && fireTime[nr][nc] == Integer.MAX_VALUE) { fireTime[nr][nc] = time; q.offer(new int[]{nr, nc}); } } } } } private boolean canReach(int[][] grid, int[][] fireTime, int waitTime) { if (waitTime >= fireTime[0][0]) return false; java.util.Queue<int[]> q = new java.util.LinkedList<>(); q.offer(new int[]{0, 0}); boolean[][] visited = new boolean[m][n]; visited[0][0] = true; int time = waitTime; while (!q.isEmpty()) { int size = q.size(); time++; for (int i = 0; i < size; i++) { int[] curr = q.poll(); for (int[] dir : dirs) { int nr = curr[0] + dir[0], nc = curr[1] + dir[1]; if (nr >= 0 && nr < m && nc >= 0 && nc < n && grid[nr][nc] == 0 && !visited[nr][nc]) { if (nr == m - 1 && nc == n - 1) { if (time <= fireTime[nr][nc]) return true; } if (time < fireTime[nr][nc]) { visited[nr][nc] = true; q.offer(new int[]{nr, nc}); } } } } } return false; }}Complexity
Time
O(M*N * log(M*N)). The `fireTime` calculation is O(M*N). The binary search performs log(M*N) iterations, and in each iteration, it calls `canReach`, which takes O(M*N).
Space
O(M*N) for the `fireTime` grid, visited array, and BFS queues.
Trade-offs
Pros
Highly efficient and significantly faster than the linear scan approach.
Guaranteed to pass within the time limits for the given constraints.
Cons
The logic is slightly more complex due to the addition of the binary search framework.
Solutions
Solution
class Solution {private int[][] grid;private boolean[][] fire;private boolean[][] vis;private final int[] dirs = {-1, 0, 1, 0, -1};private int m;private int n;public int maximumMinutes(int[][] grid) { m = grid.length; n = grid[0].length; this.grid = grid; fire = new boolean[m][n]; vis = new boolean[m][n]; int l = -1, r = m * n; while (l < r) { int mid = (l + r + 1) >> 1; if (check(mid)) { l = mid; } else { r = mid - 1; } } return l == m * n ? 1000000000 : l; }private boolean check(int t) { for (int i = 0; i < m; ++i) { Arrays.fill(fire[i], false); Arrays.fill(vis[i], false); } Deque<int[]> q1 = new ArrayDeque<>(); for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] == 1) { q1.offer(new int[]{i, j}); fire[i][j] = true; } } } for (; t > 0 && !q1.isEmpty(); --t) { q1 = spread(q1); } if (fire[0][0]) { return false; } Deque<int[]> q2 = new ArrayDeque<>(); q2.offer(new int[]{0, 0}); vis[0][0] = true; for (; !q2.isEmpty(); q1 = spread(q1)) { for (int d = q2.size(); d > 0; --d) { int[] p = q2.poll(); if (fire[p[0]][p[1]]) { continue; } for (int k = 0; k < 4; ++k) { int x = p[0] + dirs[k], y = p[1] + dirs[k + 1]; if (x >= 0 && x < m && y >= 0 && y < n && !fire[x][y] && !vis[x][y] && grid[x][y] == 0) { if (x == m - 1 && y == n - 1) { return true; } vis[x][y] = true; q2.offer(new int[]{x, y}); } } } } return false; }private Deque<int[]> spread(Deque<int[]> q) { Deque<int[]> nq = new ArrayDeque<>(); while (!q.isEmpty()) { int[] p = q.poll(); for (int k = 0; k < 4; ++k) { int x = p[0] + dirs[k], y = p[1] + dirs[k + 1]; if (x >= 0 && x < m && y >= 0 && y < n && !fire[x][y] && grid[x][y] == 0) { fire[x][y] = true; nq.offer(new int[]{x, y}); } } } return nq; }}class Solution {public: int maximumMinutes(vector<vector<int>> &grid) { int m = grid.size(), n = grid[0].size(); bool vis[m][n]; bool fire[m][n]; int dirs[5] = {-1, 0, 1, 0, -1}; auto spread = [&](queue<pair<int, int>> &q) { queue<pair<int, int>> nq; while (q.size()) { auto [i, j] = q.front(); q.pop(); for (int k = 0; k < 4; ++k) { int x = i + dirs[k], y = j + dirs[k + 1]; if (x >= 0 && x < m && y >= 0 && y < n && !fire[x][y] && grid[x][y] == 0) { fire[x][y] = true; nq.emplace(x, y); } } } return nq; }; auto check = [&](int t) { memset(vis, false, sizeof(vis)); memset(fire, false, sizeof(fire)); queue<pair<int, int>> q1; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] == 1) { q1.emplace(i, j); fire[i][j] = true; } } } for (; t && q1.size(); --t) { q1 = spread(q1); } if (fire[0][0]) { return false; } queue<pair<int, int>> q2; q2.emplace(0, 0); vis[0][0] = true; for (; q2.size(); q1 = spread(q1)) { for (int d = q2.size(); d; --d) { auto [i, j] = q2.front(); q2.pop(); if (fire[i][j]) { continue; } for (int k = 0; k < 4; ++k) { int x = i + dirs[k], y = j + dirs[k + 1]; if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && !fire[x][y] && grid[x][y] == 0) { if (x == m - 1 && y == n - 1) { return true; } vis[x][y] = true; q2.emplace(x, y); } } } } return false; }; int l = -1, r = m * n; while (l < r) { int mid = (l + r + 1) >> 1; if (check(mid)) { l = mid; } else { r = mid - 1; } } return l == m * n ? 1e9 : l; }};class Solution: def maximumMinutes(self, grid: List[List[int]]) -> int: def spread(q: Deque[int]) -> Deque[int]: nq = deque() while q: i, j = q . popleft() for a, b in pairwise(dirs): 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 nq . append((x, y)) return nq def check(t: int) -> bool: for i in range(m): for j in range(n): fire[i][j] = False q1 = deque() for i, row in enumerate(grid): for j, x in enumerate(row): if x == 1: fire[i][j] = True q1 . append((i, j)) while t and q1: q1 = spread(q1) t -= 1 if fire[0][0]: return False q2 = deque([(0, 0)]) vis = [[False] * n for _ in range(m)] vis[0][0] = True while q2: for _ in range(len(q2)): i, j = q2 . popleft() if fire[i][j]: continue for a, b in pairwise(dirs): x, y = i + a, j + b if (0 <= x < m and 0 <= y < n and not vis[x][y] and not fire[x][y] and grid[x][y] == 0): if x == m - 1 and y == n - 1: return True vis[x][y] = True q2 . append((x, y)) q1 = spread(q1) return False m, n = len(grid), len(grid[0]) l, r = - 1, m * n dirs = (- 1, 0, 1, 0, - 1) fire = [[False] * n for _ in range(m)] while l < r: mid = (l + r + 1) >> 1 if check(mid): l = mid else: r = mid - 1 return int(1e9) if l == m * n else lVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.