【二分答案】【BFS】【数组】【2023-11-09】
2258. 逃离火灾
现在有一个人在一个二维网格的左上角,坐标 (0, 0)
处,他想安全的到达位于网格右下角 (m-1, n-1)
处的安全屋,其中 m
为网格的行数,n
为网格的列数。
网格的每个格子中有以下三种数值:
0
表示草地;1
表示着火的格子;2
表示一座墙,人和火都不能通过。人每一分钟可以向相邻的格子行走,火可以向相邻的格子扩散,人和火都会被墙阻挡(也就是有墙的格子,人和火都无法到达)。现在需要你判断人在初始位置最多停留多长时间再出发可以安全到达安全屋。如果无法实现,请返回 -1
。如果不管停留多长时间,人总是可以到达安全屋,请你返回 1 0 9 10^9 109。
我们可以使用二分枚举答案来解决该题。
为什么可以二分枚举答案?单调性如何保证?
如果人可以在初始位置停留 t
分钟,那么肯定可以停留至少 t
分钟;如果人不能在初始位置停留 t
分钟,那么可以停留的时间肯定不能超过 t
分钟。于是可以想到二分枚举答案,如果人可以在初始位置停留 t
分钟,那么接下来就在 t
的右侧进行枚举,否则在 t
的左侧进行枚举。我们通过函数 check()
来判断停留 t
分钟是否安全。
二分枚举答案的上限是什么?
如图,火可能要绕很多圈才能到达左上角,人可以在被火烧到的前一分钟出发。所以粗略估计,就用 m*n
当作二分的上限。
check()
如何实现?
假设当前二分枚举的答案是 t
。不管是火的扩散还是人的行走都可以使用 BFS
来实现。
人在前往安全屋之前停留 t
分钟,在这 t
分钟内火向四周扩散。接着在每分钟内人先移动,火再移动,如果人遇到着火的格子那就跳过,不走这个格子。
如果,人最后可以到达安全屋,则说明答案至少为 t
,否则答案小于 t
。
二分枚举逻辑代码
二分的下限 left
为 0
。上限 right
前面已经分析了是 mn
。答案 res
初始化为 -1
。
在 [left, right]
中进行迭代二分,while(left <= right)
:
mid = (left + right) / 2
;check(mid)
为 true
,则更新 res = mid
且 left = mid + 1
;否则更新 right = mid - 1
;left
。最终的答案还要比较一下 res
是否小于 m*n
,如是返回 left
,否则返回 1 0 9 10^9 109。
实现代码
class Solution {
const int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public:
bool check(vector<vector<int>>& grid, int t) {
int m = grid.size(), n = grid[0].size();
vector<vector<int>>on_fire(m, vector<int>(n)); // 记录着火的格子
vector<pair<int, int>> f; // 两个数组来代替队列
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 1) {
on_fire[i][j] = 1;
f.emplace_back(i, j); // 或者 f.push_back({i, j});
}
}
}
// 火扩散的 BFS
auto spread_fire = [&]() {
vector<pair<int, int>> nf; // f 和 nf 配合取代队列
for (auto& [i, j] : f) {
for (auto& [dx, dy] :dirs) {
int x = i + dx, y = j + dy;
if (0 <= x && x < m && 0 <= y && y < n && !on_fire[x][y] && grid[x][y] == 0) {
on_fire[x][y] = 1;
nf.emplace_back(x, y);
}
}
}
f = move(nf);
};
// t 分钟内火向四周扩散
while (t -- && !f.empty()) {
spread_fire();
}
if (on_fire[0][0]) return false; // 初始位置着火
// 人和火先后扩散
vector<vector<int>> vis(m, vector<int>(n)); // 防止人重复走同一个格子
vis[0][0] = 1;
vector<pair<int, int>> q{{0, 0}};
while (!q.empty()) {
vector<pair<int, int>> nq;
for (auto& [i, j] : q) {
if (on_fire[i][j]) continue;
for (auto& [dx, dy] : dirs) {
int x = i + dx, y = j + dy;
if (0 <= x && x < m && 0 <= y && y < n && !on_fire[x][y] && grid[x][y] == 0 && !vis[x][y]) {
if (x == m - 1 && y == n - 1) {
return true;
}
vis[x][y] = 1;
nq.emplace_back(x, y);
}
}
}
q = move(nq);
spread_fire();
}
return false;
}
int maximumMinutes(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
int left = 0, right = m * n;
int res = -1;
while (left <= right) {
int mid = (left + right) >> 1;
if (check(grid, mid)) {
res = mid;
left = mid + 1;
}
else {
right = mid - 1;
}
}
return res < m * n ? res : 1e9;
}
};
复杂度分析
时间复杂度: O ( m n l o g m n ) O(mnlogmn) O(mnlogmn), m m m 和 n n n 分别为 grid
的行数和列数,二分枚举 O ( l o g m n ) O(logmn) O(logmn) 次,每次 check()
的时间为 O ( m n ) O(mn) O(mn)。
如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 。
如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。
最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 哦。