Problem: Escape the Spreading Fire

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:

* `0` represents grass,
* `1` represents fire,
* `2` represents 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).


Python solution:
import math
import itertools
import bisect
import collections
import string
import heapq
import functools
import sortedcontainers
from typing import List, Dict, Tuple, Iterator

class Solution:
  def maximumMinutes(self, grid: List[List[int]]) -> int:
    dirs = ((0, 1), (1, 0), (0, -1), (-1, 0))
    kMax = len(grid) * len(grid[0])
    fireGrid = [[-1] * len(grid[0]) for _ in range(len(grid))]
    self._buildFireGrid(grid, fireGrid, dirs)

    ans = -1
    l = 0
    r = kMax

    while l <= r:
      m = (l + r) // 2
      if self._canStayFor(grid, fireGrid, m, dirs):
        ans = m
        l = m + 1
      else:
        r = m - 1

    return int(1e9) if ans == kMax else ans

  def _buildFireGrid(self, grid: List[List[int]], fireMinute: List[List[int]], dirs: List[int]) -> None:
    minuteFromFire = 0
    q = collections.deque()

    for i in range(len(grid)):
      for j in range(len(grid[0])):
        if grid[i][j] == 1:
          q.append((i, j))
          fireMinute[i][j] = 0
    while q:
      minuteFromFire += 1
      for _ in range(len(q)):
        i, j = q.popleft()
        for dx, dy in dirs:
          x = i + dx
          y = j + dy
          if x < 0 or x == len(grid) or y < 0 or y == len(grid[0]):
            continue
          if grid[x][y] == 2:
            continue
          if fireMinute[x][y] != -1:
            continue
          fireMinute[x][y] = minuteFromFire
          q.append((x, y))

  def _canStayFor(self, grid: List[List[int]], fireMinute: List[List[int]], minute: int, dirs: List[int]) -> bool:
    q = collections.deque([(0, 0)])
    seen = {(0, 0)}

    while q:
      minute += 1
      for _ in range(len(q)):
        i, j = q.popleft()
        for dx, dy in dirs:
          x = i + dx
          y = j + dy
          if x < 0 or x == len(grid) or y < 0 or y == len(grid[0]):
            continue
          if grid[x][y] == 2:
            continue
          if x == len(grid) - 1 and y == len(grid[0]) - 1:
            if fireMinute[x][y] != -1 and fireMinute[x][y] < minute:
              continue
            return True
          if fireMinute[x][y] != -1 and fireMinute[x][y] <= minute:
            continue
          if (x, y) in seen:
            continue
          q.append((x, y))
          seen.add((x, y))

    return False


Java solution:
class Solution {
  public int maximumMinutes(int[][] grid) {
    final int MAX = grid.length * grid[0].length;
    int[][] fireMinute = new int[grid.length][grid[0].length];
    Arrays.stream(fireMinute).forEach(A -> Arrays.fill(A, -1));
    buildFireGrid(grid, fireMinute);

    int ans = -1;
    int l = 0;
    int r = MAX;

    while (l <= r) {
      final int m = (l + r) / 2;
      if (canStayFor(grid, fireMinute, m)) {
        ans = m;
        l = m + 1;
      } else {
        r = m - 1;
      }
    }

    return ans == MAX ? 1_000_000_000 : ans;
  }

  private static final int[][] DIRS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

  private void buildFireGrid(int[][] grid, int[][] fireMinute) {
    Queue<Pair<Integer, Integer>> q = new ArrayDeque<>();

    for (int i = 0; i < grid.length; ++i)
      for (int j = 0; j < grid[0].length; ++j)
        if (grid[i][j] == 1) { // the fire
          q.offer(new Pair<>(i, j));
          fireMinute[i][j] = 0;
        }

    for (int minuteFromFire = 1; !q.isEmpty(); ++minuteFromFire)
      for (int sz = q.size(); sz > 0; --sz) {
        final int i = q.peek().getKey();
        final int j = q.poll().getValue();
        for (int[] dir : DIRS) {
          final int x = i + dir[0];
          final int y = j + dir[1];
          if (x < 0 || x == grid.length || y < 0 || y == grid[0].length)
            continue;
          if (grid[x][y] == 2) // the wall
            continue;
          if (fireMinute[x][y] != -1)
            continue;
          fireMinute[x][y] = minuteFromFire;
          q.offer(new Pair<>(x, y));
        }
      }
  }

  boolean canStayFor(int[][] grid, int[][] fireMinute, int minute) {
    Queue<Pair<Integer, Integer>> q =
        new ArrayDeque<>(List.of(new Pair<>(0, 0))); // the start position
    boolean[][] seen = new boolean[grid.length][grid[0].length];
    seen[0][0] = true;

    while (!q.isEmpty()) {
      ++minute;
      for (int sz = q.size(); sz > 0; --sz) {
        final int i = q.peek().getKey();
        final int j = q.poll().getValue();
        for (int[] dir : DIRS) {
          final int x = i + dir[0];
          final int y = j + dir[1];
          if (x < 0 || x == grid.length || y < 0 || y == grid[0].length)
            continue;
          if (grid[x][y] == 2) // the wall
            continue;
          if (x == grid.length - 1 && y == grid[0].length - 1) {
            if (fireMinute[x][y] != -1 && fireMinute[x][y] < minute)
              continue;
            return true;
          }
          if (fireMinute[x][y] != -1 && fireMinute[x][y] <= minute)
            continue;
          if (seen[x][y])
            continue;
          q.offer(new Pair<>(x, y));
          seen[x][y] = true;
        }
      }
    }

    return false;
  }
}


C++ solution:
class Solution {
 public:
  int maximumMinutes(vector<vector<int>>& grid) {
    const int kMax = grid.size() * grid[0].size();
    vector<vector<int>> fireMinute(grid.size(),
                                   vector<int>(grid[0].size(), -1));
    buildFireGrid(grid, fireMinute);

    int ans = -1;
    int l = 0;
    int r = kMax;

    while (l <= r) {
      const int m = (l + r) / 2;
      if (canStayFor(grid, fireMinute, m)) {
        ans = m;
        l = m + 1;
      } else {
        r = m - 1;
      }
    }

    return ans == kMax ? 1'000'000'000 : ans;
  }

 private:
  static constexpr int kDirs[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

  void buildFireGrid(const vector<vector<int>>& grid,
                     vector<vector<int>>& fireMinute) {
    queue<pair<int, int>> q;

    for (int i = 0; i < grid.size(); ++i)
      for (int j = 0; j < grid[0].size(); ++j)
        if (grid[i][j] == 1) {  // the fire
          q.emplace(i, j);
          fireMinute[i][j] = 0;
        }

    for (int minuteFromFire = 1; !q.empty(); ++minuteFromFire)
      for (int sz = q.size(); sz > 0; --sz) {
        const auto [i, j] = q.front();
        q.pop();
        for (const auto& [dx, dy] : kDirs) {
          const int x = i + dx;
          const int y = j + dy;
          if (x < 0 || x == grid.size() || y < 0 || y == grid[0].size())
            continue;
          if (grid[x][y] == 2)  // the wall
            continue;
          if (fireMinute[x][y] != -1)
            continue;
          fireMinute[x][y] = minuteFromFire;
          q.emplace(x, y);
        }
      }
  }

  bool canStayFor(const vector<vector<int>>& grid,
                  const vector<vector<int>>& fireMinute, int minute) {
    queue<pair<int, int>> q{{{0, 0}}};  // the start position
    vector<vector<bool>> seen(grid.size(), vector<bool>(grid[0].size()));
    seen[0][0] = true;

    while (!q.empty()) {
      ++minute;
      for (int sz = q.size(); sz > 0; --sz) {
        const auto [i, j] = q.front();
        q.pop();
        for (const auto& [dx, dy] : kDirs) {
          const int x = i + dx;
          const int y = j + dy;
          if (x < 0 || x == grid.size() || y < 0 || y == grid[0].size())
            continue;
          if (grid[x][y] == 2)  // the wall
            continue;
          if (x == grid.size() - 1 && y == grid[0].size() - 1) {
            if (fireMinute[x][y] != -1 && fireMinute[x][y] < minute)
              continue;
            return true;
          }
          if (fireMinute[x][y] != -1 && fireMinute[x][y] <= minute)
            continue;
          if (seen[x][y])
            continue;
          q.emplace(x, y);
          seen[x][y] = true;
        }
      }
    }

    return false;
  }
};