【每日一题】逃离火灾

文章目录

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

写在最后

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

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

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

相关推荐
Moshow郑锴4 天前
Spring Boot中CollectionUtils怎么用
springboot·数组·collectionutil
LuckyLay4 天前
Golang学习笔记_13——数组
笔记·学习·golang·数组·array
暂时先用这个名字7 天前
PHP开发日志 ━━ 基础知识:四种不同的变量返回方式该如何调用
android·开发语言·缓存·php·框架·变量·数组
DogDaoDao11 天前
leetcode 面试经典 150 题:移除元素
算法·leetcode·面试·数组·双指针·快慢指针·数据结构与算法
闻缺陷则喜何志丹13 天前
【C++图论 BFS算法】2467. 树上最大得分和路径|2053
c++·算法·力扣·图论·bfs·路径·最大
繁星璀璨G14 天前
Lua语言入门 - Lua 数组
开发语言·lua·数组
软件架构师笔记15 天前
深入浅出 Go 语言:数组与切片
数据结构·算法·go·数组·切片
三月微暖寻春笋17 天前
【和春笋一起学C++】OpenCV中数组和指针运用实例
opencv·指针·数组·二值化·遍历像素
Lumos_yuan21 天前
Lumos学习王佩丰Excel第十八讲:LOOKUP函数与数组
学习·excel·数组·lookup函数
IronmanJay23 天前
【LeetCode每日一题】——189.轮转数组
数据结构·算法·leetcode·数组·189.轮转数组