Facebook Pixel

2258. Escape the Spreading Fire

Problem Description

You are given a 2D grid representing a field where:

  • 0 represents grass cells you can walk on
  • 1 represents fire cells that will spread
  • 2 represents 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:

  1. Initially, you can choose to stay at your starting position for some number of minutes
  2. Each minute you stay, the fire spreads to all adjacent grass cells (not through walls)
  3. Once you start moving, you can move to an adjacent grass cell each minute
  4. After each of your moves, the fire spreads again to adjacent grass cells
  5. 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 -1 if it's impossible to reach the safehouse at all
  • Return 10^9 if 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:

  1. We need to simulate the fire spreading level by level (minute by minute)
  2. We need to find if a path exists from start to end position
  3. The graph is unweighted (each move takes 1 minute)
  4. 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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Fire spreading phase: First, simulate the fire spreading for mid minutes while we wait at the starting position. We use BFS to spread fire level by level, where each level represents one minute.

  2. 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 d before moving to distance d+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 × n minutes and still escape, we can wait indefinitely (return 10^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: 0 to m * n (maximum possible waiting time)
  • Inverted condition: cannot_escape(mid) returns true when we cannot escape with mid minutes wait
  • Pattern: false, false, ..., true, true (can escape, can escape, ..., cannot escape, cannot escape)
  • Answer: first_true_index - 1 gives 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, return 10^9
  • Otherwise: Return l as 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 Evaluator

Example 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, so l == 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
114
1class 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}
151
1class 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};
125
1/**
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}
156

Time 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 t minutes: Each spread operation processes at most O(m × n) cells, and we do this at most t times where t ≤ m × n, giving O(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 the fire and vis arrays.

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:

  • fire array: O(m × n) space to track cells on fire
  • vis array: O(m × n) space to track visited cells during BFS
  • Two deques q1 and q2: In the worst case, these can contain O(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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which two pointer techniques do you use to check if a string is a palindrome?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More