🔥 逃离火灾
📝 题目描述
给你一个下标从0开始,大小为m x n
的二维整数数组grid
,它表示一个网格图。每个格子为下面3个值之一:
0
表示草地。1
表示着火的格子。2
表示一座墙,你跟火都不能通过这个格子。
一开始你在最左上角的格子(0, 0)
,你想要到达右下角的安全屋格子(m - 1, n - 1)
。每一分钟,你可以移动到相邻的草地格子。每次你移动之后,着火的格子会扩散到所有不是墙的相邻格子。
请你返回你在初始位置可以停留的最多分钟数,且停留完这段时间后你还能安全到达安全屋。如果无法实现,请你返回
注意,如果你到达安全屋后,火马上到了安全屋,这视为你能够安全到达安全屋。
如果两个格子有共同边,那么它们为相邻格子。
📋 代码模板
class Solution {
public int maximumMinutes(int[][] grid) {
}
}
function maximumMinutes(grid: number[][]): number {
}
💡 提示
是 0
,1
或者2
🚀 示例
示例一
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]]
。在该网格上,人在初始位置最多等待3
分钟,因此答案为3
。
示例二
grid
输入为[[0,0,0,0],[0,1,2,0],[0,2,0,0]]
。在该网格上,人无论怎么向外移动,都会被火烧到,因此答案为-1
。
示例三
grid
输入为[[0,0,0],[2,2,0],[1,2,0]]
。在该网格上,火会被墙围住,永远蔓延不出来,因此答案为1000000000
。
🖊️ 题解
二分查找 + BFS
假设人在初始位置停留
二分查找的下限可以设置为
那么我们如何来验证人在停留
在BFS过程中,人不能走“超出网格范围”、“已经着火”、“已经走过”、“表示墙”的单元格,火不能向“超出网格范围”、“已经着火”、“表示墙”的单元格蔓延。
需要注意的是,如果最后二分查找得到的答案是
class Solution {
private final static int[][] DIRS = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
private final static int FIRE = 1;
private final static int GRASS = 0;
public int maximumMinutes(int[][] grid) {
int m = grid.length, n = grid[0].length;
int low = 0, high = m * n;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (check(grid, mid)) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return high == m * n ? (int) 1e9 : high;
}
private boolean check(int[][] grid, int t) {
int m = grid.length, n = grid[0].length;
boolean[][] onFire = new boolean[m][n];
Queue<int[]> fireQueue = new LinkedList<>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == FIRE) {
fireQueue.offer(new int[]{i, j});
onFire[i][j] = true;
}
}
}
spreadFire(grid, onFire, fireQueue, t);
boolean[][] passBy = new boolean[m][n];
Queue<int[]> personQueue = new LinkedList<>();
personQueue.offer(new int[]{0, 0});
passBy[0][0] = true;
while (!personQueue.isEmpty()) {
int size = personQueue.size();
while (size > 0) {
size--;
int[] cell = personQueue.poll();
int oldI = cell[0], oldJ = cell[1];
if (onFire[oldI][oldJ]) {
continue;
}
for (int d = 0; d < 4; d++) {
int i = oldI + DIRS[d][0], j = oldJ + DIRS[d][1];
if (!inRange(grid, i, j) || onFire[i][j] || passBy[i][j] || grid[i][j] != GRASS) {
continue;
}
if (i == m - 1 && j == n - 1) {
return true;
}
personQueue.offer(new int[]{i, j});
passBy[i][j] = true;
}
}
spreadFire(grid, onFire, fireQueue, 1);
}
return false;
}
private void spreadFire(int[][] grid, boolean[][] onFire, Queue<int[]> fireQueue, int t) {
while (!fireQueue.isEmpty() && t > 0) {
int size = fireQueue.size();
while (size > 0) {
int[] cell = fireQueue.poll();
int oldI = cell[0], oldJ = cell[1];
for (int d = 0; d < 4; d++) {
int i = oldI + DIRS[d][0], j = oldJ + DIRS[d][1];
if (inRange(grid, i, j) && grid[i][j] == GRASS && !onFire[i][j]) {
fireQueue.offer(new int[]{i, j});
onFire[i][j] = true;
}
}
size--;
}
t--;
}
}
private boolean inRange(int[][] grid, int i, int j) {
int m = grid.length, n = grid[0].length;
return i >= 0 && i < m && j >= 0 && j < n;
}
}
const DIRS: number[][] = [[-1, 0], [1, 0], [0, -1], [0, 1]];
const GRASS = 0, FIRE = 1;
function maximumMinutes(grid: number[][]): number {
const m = grid.length, n = grid[0].length;
const inRange = (i: number, j: number) => {
return i >= 0 && i < m && j >= 0 && j < n;
}
const check = (t: number): boolean => {
const onFire: boolean[][] = Array.from({length: m}, () => Array(n).fill(false));
const fireQueue: number[][] = [];
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (grid[i][j] == FIRE) {
fireQueue.push([i, j]);
onFire[i][j] = true;
}
}
}
const spreadFire = (t: number): void => {
while (fireQueue.length > 0 && t > 0) {
let size = fireQueue.length;
while (size > 0) {
const cell: number[] = fireQueue.shift()!;
const oldI = cell[0], oldJ = cell[1];
for (let d = 0; d < 4; d++) {
const i = oldI + DIRS[d][0], j = oldJ + DIRS[d][1];
if (inRange(i, j) && grid[i][j] == GRASS && !onFire[i][j]) {
fireQueue.push([i, j]);
onFire[i][j] = true;
}
}
size--;
}
t--;
}
}
spreadFire(t);
const passBy: boolean[][] = Array.from({length: m}, () => Array(n).fill(false));
const personQueue: number[][] = [];
personQueue.push([0, 0]);
passBy[0][0] = true;
while (personQueue.length > 0) {
let size = personQueue.length;
while (size > 0) {
size--;
const cell: number[] = personQueue.shift()!;
const oldI = cell[0], oldJ = cell[1];
if (onFire[oldI][oldJ]) {
continue;
}
for (let d = 0; d < 4; d++) {
const i = oldI + DIRS[d][0], j = oldJ + DIRS[d][1];
if (!inRange(i, j) || passBy[i][j] || onFire[i][j] || grid[i][j] != GRASS) {
continue;
}
if (i == m - 1 && j == n - 1) {
return true;
}
personQueue.push([i, j]);
passBy[i][j] = true;
}
}
spreadFire(1);
}
return false;
}
let low = 0, high = m * n;
while (low <= high) {
const mid = low + Math.floor((high - low) / 2);
if (check(mid)) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return high == m * n ? 1e9 : high;
}
BFS + 分类讨论
将网格中的一个草地格子记为
火和人不能在草地同时相遇,那如果
在以上这种特殊情况下,人可以在初始位置最多等待
- 如果
,则说明人无法到达安全屋,返回 。 - 否则,如果
,则说明火无法到达安全屋,而由于此时人和安全屋是连通的,火和安全屋又不是连通的,因此人和火是不连通的,火永远烧不到人,人可以在初始位置无限等待下去,返回 。 - 否则,记
。 - 如果
,说明火比人先到安全屋,返回 。 - 如果
,判断人能否在初始位置停留 分钟后,先比火到达安全屋左边或上边的相邻格子。如果可以,返回 ,相反,返回 。
class Solution {
private final static int[][] DIRS = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
private final static int NOT_VISITED = -1;
private final static int GRASS = 0, FIRE = 1;
public int maximumMinutes(int[][] grid) {
int m = grid.length, n = grid[0].length;
List<int[]> personStart = new ArrayList<>(), fireStart = new ArrayList<>();
personStart.add(new int[]{0, 0});
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == FIRE) {
fireStart.add(new int[]{i, j});
}
}
}
int[][] personTime = bfs(grid, personStart), fireTime = bfs(grid, fireStart);
if (personTime[m - 1][n - 1] == NOT_VISITED) {
return -1;
}
if (fireTime[m - 1][n - 1] == NOT_VISITED) {
return (int) 1e9;
}
int dis = fireTime[m - 1][n - 1] - personTime[m - 1][n - 1];
if (dis < 0) {
return -1;
}
int fireTop = fireTime[m - 2][n - 1], fireLeft = fireTime[m - 1][n - 2];
int personTop = personTime[m - 2][n - 1], personLeft = personTime[m - 1][n - 2];
if ((fireTop != NOT_VISITED && personTop + dis < fireTop) ||
(fireLeft != NOT_VISITED && personLeft + dis < fireLeft)) {
return dis;
}
return dis - 1;
}
private int[][] bfs(int[][] grid, List<int[]> start) {
int m = grid.length, n = grid[0].length;
int[][] time = new int[m][n];
for (int[] t : time) {
Arrays.fill(t, NOT_VISITED);
}
Queue<int[]> queue = new LinkedList<>();
for (int[] s : start) {
queue.offer(s);
time[s[0]][s[1]] = 0;
}
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int oldI = cell[0], oldJ = cell[1];
for (int d = 0; d < 4; d++) {
int i = oldI + DIRS[d][0], j = oldJ + DIRS[d][1];
if (i < 0 || i > m - 1 || j < 0 || j > n - 1
|| time[i][j] != NOT_VISITED || grid[i][j] != GRASS) {
continue;
}
queue.offer(new int[]{i, j});
time[i][j] = time[oldI][oldJ] + 1;
}
}
return time;
}
}
const DIRS: number[][] = [[-1, 0], [1, 0], [0, -1], [0, 1]];
const NOT_VISITED = -1;
const GRASS = 0, FIRE = 1;
function maximumMinutes(grid: number[][]): number {
const m = grid.length, n = grid[0].length;
const bfs = (start: number[][]): number[][] => {
const time: number[][] = Array.from({length: m}, () => Array(n).fill(NOT_VISITED));
const queue: number[][] = [];
for (const s of start) {
queue.push(s);
time[s[0]][s[1]] = 0;
}
while (queue.length > 0) {
const cell: number[] = queue.shift()!;
const oldI = cell[0], oldJ = cell[1];
for (let d = 0; d < 4; d++) {
const i = oldI + DIRS[d][0], j = oldJ + DIRS[d][1];
if (i < 0 || i > m - 1 || j < 0 || j > n - 1
|| time[i][j] != NOT_VISITED || grid[i][j] != GRASS) {
continue;
}
queue.push([i, j]);
time[i][j] = time[oldI][oldJ] + 1;
}
}
return time;
}
const personStart: number[][] = [[0, 0]], fireStart: number[][] = [];
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (grid[i][j] == FIRE) {
fireStart.push([i, j]);
}
}
}
const personTime = bfs(personStart), fireTime = bfs(fireStart);
if (personTime[m - 1][n - 1] == NOT_VISITED) {
return -1;
}
if (fireTime[m - 1][n - 1] == NOT_VISITED) {
return 1e9;
}
const dis = fireTime[m - 1][n - 1] - personTime[m - 1][n - 1];
if (dis < 0) {
return -1;
}
const fireTop = fireTime[m - 2][n - 1], personTop = personTime[m - 2][n - 1];
const fireLeft = fireTime[m - 1][n - 2], personLeft = personTime[m - 1][n - 2];
if ((fireTop != NOT_VISITED && personTop + dis < fireTop)
|| (fireLeft != NOT_VISITED && personLeft + dis < fireLeft)) {
return dis;
}
return dis - 1;
}
💭 复杂度分析
基于二分查找 + BFS
的解决方案的复杂度分析如下。
时间复杂度:
,其中 和 分别为 的行数和列数。二分查找循环 次,每次 check
的时间复杂度为。 空间复杂度:
。
基于BFS + 分类讨论
的解决方案的复杂度分析如下。
时间复杂度:
,其中 和 分别为 的行数和列数。 空间复杂度:
。