【每日一题】逃离火灾

文章目录

Tag

【二分答案】【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

二分枚举逻辑代码

二分的下限 left0。上限 right 前面已经分析了是 mn。答案 res 初始化为 -1

[left, right] 中进行迭代二分,while(left <= right)

  • mid = (left + right) / 2
  • 如果 check(mid)true,则更新 res = midleft = mid + 1;否则更新 right = mid - 1
  • 最后返回 left

最终的答案还要比较一下 res 是否小于 m*n,如是返回 left,否则返回 1 0 9 10^9 109。

实现代码

cpp 复制代码
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)。

空间复杂度: O ( m n ) O(mn) O(mn)。

写在最后

如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。

最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。

相关推荐
Alfred king19 小时前
面试150 搜索二维矩阵
线性代数·矩阵·二分查找·数组
Alfred king20 小时前
面试150 IPO
面试·职场和发展·贪心·数组··排序
Alfred king2 天前
面试150 建立四叉树
矩阵··数组·分治
Alfred king4 天前
面试150 环形子数组的最大和
面试·职场和发展·数组·队列·分治
Alfred king23 天前
面试150 生命游戏
leetcode·游戏·面试·数组
柏箱1 个月前
容器里有10升油,现在只有两个分别能装3升和7升油的瓶子,需要将10 升油等分成2 个5 升油。程序输出分油次数最少的详细操作过程。
算法·bfs
芜湖xin1 个月前
【题解-洛谷】B4292 [蓝桥杯青少年组省赛 2022] 路线
算法·图论·bfs·图的遍历
laufing2 个月前
OD 算法题 B卷【最大岛屿体积】
bfs
无影无踪的青蛙2 个月前
[C++]洛谷B3626 跳跃机器人(题干 + 详细讲解, BFS练习题)
开发语言·c++·算法·bfs·广度优先
Tee xm2 个月前
算法修仙传 第一章 灵根觉醒:数组基础与遍历
java·数据结构·算法·数组·遍历