题目描述:
给你一个下标从 0 开始大小为
m x n
的二维整数数组grid
,它表示一个网格图。每个格子为下面 3 个值之一:
0
表示草地。1
表示着火的格子。2
表示一座墙,你跟火都不能通过这个格子。一开始你在最左上角的格子
(0, 0)
,你想要到达最右下角的安全屋格子(m - 1, n - 1)
。每一分钟,你可以移动到 相邻 的草地格子。每次你移动 之后 ,着火的格子会扩散到所有不是墙的 相邻 格子。请你返回你在初始位置可以停留的 最多 分钟数,且停留完这段时间后你还能安全到达安全屋。如果无法实现,请你返回
-1
。如果不管你在初始位置停留多久,你 总是 能到达安全屋,请你返回109
。注意,如果你到达安全屋后,火马上到了安全屋,这视为你能够安全到达安全屋。
如果两个格子有共同边,那么它们为 相邻 格子。
前提:如果人可以在初始位置停留 3
分钟,那同样也可以在初始位置停留 0,1,2
分钟;人不能在初始位置停留 4
分钟,那同样也不能在初始位置停留 5,6,7,⋯
分钟。因此可以考虑使用二分答案的做法,即假设答案为 t
,判断是否可行。
判断函数 check
的逻辑:
- 用一个二维数据
onFire
记录当前哪些位置已经着火; - 人现在原地停留
t
分钟,因此通过 BFS 让火扩散t
分钟,此时如果烧到了起点,证明check
失败,要减小t
; - 然后每分钟人先移动,火再移动。如果下一分钟人的格子出队时,发现已经被火烧到了,则直接跳过。如果人可以到达安全屋,则说明
check
成功,要增大t
。
class Solution {
private static final int[][] DIRS = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
private boolean check(int[][] grid, int min) {
int m = grid.length, n = grid[0].length;
// 用二维数组表示现在的火势
boolean[][] onFire = new boolean[m][n];
// 用数组来模拟 BFS,和队列貌似区别不大
List<int[]> f = new ArrayList<>();
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(grid[i][j] == 1) {
onFire[i][j] = true;
f.add(new int[]{i, j});
}
}
}
// 火先扩散 min 分钟
while(min-- > 0 && !f.isEmpty()) {
f = spreadFire(grid, onFire, f);
}
// min 分钟后如果火势扩散到了起点直接返回失败
if(onFire[0][0]) return false;
List<int[]> p = new ArrayList<>();
boolean[][] vis = new boolean[m][n];
p.add(new int[]{0, 0});
vis[0][0] = true;
// 经典的 BFS 写法
while(!p.isEmpty()) {
List<int[]> tmp = p;
p = new ArrayList<>();
for(int[] pos : tmp) {
int x = pos[0], y = pos[1];
// 当前位置已经被火烧了
if(onFire[x][y]) continue;
for(int[] d : DIRS) {
int nx = x + d[0], ny = y + d[1];
if(nx >= 0 && nx < m && ny >= 0 && ny < n && !onFire[nx][ny] && !vis[nx][ny] && grid[nx][ny] == 0) {
// 只要有一个机会能走到,就返回成功
if(nx == m - 1 && ny == n - 1) return true;
p.add(new int[]{nx, ny});
vis[nx][ny] = true;
}
}
}
f = spreadFire(grid, onFire, f);
}
return false;
}
// 火的扩散 BFS 函数
private List<int[]> spreadFire(int[][] grid, boolean[][] onFire, List<int[]> f) {
int m = grid.length, n = grid[0].length;
List<int[]> tmp = f;
f = new ArrayList<>();
for(int[] pos : tmp) {
int x = pos[0], y = pos[1];
for(int[] d : DIRS) {
int nx = x + d[0], ny = y + d[1];
if(nx >= 0 && nx < m && ny >= 0 && ny < n && !onFire[nx][ny] && grid[nx][ny] == 0) {
onFire[nx][ny] = true;
f.add(new int[]{nx, ny});
}
}
}
return f;
}
public int maximumMinutes(int[][] grid) {
int m = grid.length, n = grid[0].length;
int left = 0, right = m * n;
// 二分查找的闭区间写法
while(left <= right) {
int mid = (left + right) >> 1;
if(check(grid, mid)) left = mid + 1;
else right = mid - 1;
}
// left > m * n 说明不管停留多久都行
return left > m * n ? 1000000000 : right;
}
}