【每日一题】逃离火灾

文章目录

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)。

写在最后

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

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

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

相关推荐
ankleless19 小时前
C语言(11)—— 数组(超绝详细总结)
c语言·零基础·数组·二维数组·自学·一维数组
梁辰兴2 天前
数据结构:串、数组与广义表
开发语言·数据结构·c··数组·广义表
是阿建吖!4 天前
【递归、搜索与回溯算法】穷举、暴搜、深搜、回溯、剪枝
算法·bfs·剪枝
没有bug.的程序员7 天前
《常见高频算法题 Java 解法实战精讲(1):链表与数组》
java·算法·链表·数组
CUC-MenG12 天前
2025牛客多校第五场 K.完美旅程 J.最快覆盖问题 E.神秘异或操作 个人题解
数学·dfs·bfs·优先队列·二分·位运算·fmt·曼哈顿距离·fwt
意法半导体STM3218 天前
STM32CubeMX 生成时钟获取函数的分析
mcu·stm32cubemx·数组·st·意法半导体·hal 时钟获取函数
Espresso Macchiato18 天前
Leetcode 3629. Minimum Jumps to Reach End via Prime Teleportation
bfs·广度优先遍历·leetcode medium·leetcode 3629·leetcode周赛460·质数求解·质因素分解
Alfred king18 天前
Leetcode 四数之和
算法·leetcode·职场和发展·数组·排序·双指针
Alfred king19 天前
面试150 不同路径Ⅱ
矩阵·动态规划·数组
SoveTingღ19 天前
【C语言】数组和指针一样吗?
c语言·unix·指针·数组·嵌入式软件