LeetCode 2258 逃离火灾现场

https://leetcode.com/problems/escape-the-spreading-fire/solutions/1994594/python-explanation-with-pictures-two-bfs-solutions/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china

题目给的还是比较绕的,非要说什么人可以等几分钟出发。初始状态,是 人、火源、防火墙 的位置点。每分钟发生的时候,火会往之前没有着的格子(不能是墙)烧;人可以去没有火的地方。

Bakerston 的回答非常漂亮,而且图文并茂。一起学习一下:

方法1:广度优先搜索 + 二分查找

用 BFS 来找到并更新着火的格子和安全的格子:

第一天,火从右上角,向左边和下面蔓延;人可去到下面和右边;

第二天,火接着向 左、下蔓延;第一排的人没有办法向右,只能向下;

同理来到第三天。

然后我们怎么知道我们可以到达绿色的安全格子呢?

因为每一天,火在我们移动前蔓延的,所以:

  • 如果我们在火烧了绿格子的一天及之后到达,fail,失败;【题目中有说:注意,如果你到达安全屋后,火马上到了安全屋,这视为你能够安全到达安全屋。】
  • 如果在同一天或提前,success,成功。

看啊,就是上面所画的。

接下来回到问题,我们看看可以停留多久:

假设我们待 x 天会失败,那我们应该待得更短: x-1 天;

假如我们待 x 天会成功,那我们应该待得更长一点: x+1 天。

这就变成了一个经典的二分搜索问题。

大佬谦虚了,说代码有点丑陋:

def maximumMinutes(self, A: List[List[int]]) -> int:
        m, n = len(A), len(A[0])
        fires, seen = [], set()
        f = collections.deque()
        for i in range(m):
            for j in range(n):
                if A[i][j] == 1:  # 1 表示着火的格子
                    fires.append((i, j))   
                    seen.add((i, j))  # set
                    f.append((i, j))  # deque
        dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)]   # 移动方向

        # If we stay for 'days' days.
        def helper(days):
            fire, seen = fires[::], set()
            
            # Let the fire spreads for 'days' days.
            while days > 0:
                newfire = []
                for i, j in fire:
                    for di, dj in dirs:
                        ni, nj = i + di, j + dj
                        if 0 <= ni < m and 0 <= nj < n and A[ni][nj] == 0 and (ni, nj) not in seen:
                            # If the fire reach us before we move, we fail.
                            if ni == 0 and nj == 0:
                                return False
                            seen.add((ni, nj))
                            newfire.append((ni, nj))
                fire = newfire[::]
                days -= 1

            # Then let the fire and us move by turn (fire first).
            safe = [(0, 0)]
            while safe:
                
                # Fire spreads first.
                newfire = []
                for i, j in fire:
                    for di, dj in dirs:
                        ni, nj = i + di, j + dj
                        if 0 <= ni < m and 0 <= nj < n and A[ni][nj] == 0 and (ni, nj) not in seen:
                            # If the fire reaches bot-right cell, if we are just one step close to bot-right cell
                            # We can still reach it, otherwise we fail. (Please refer to picture 2)
                            if ni == m - 1 and nj == n - 1:
                                if not ((m - 2, n - 1) in safe or (m - 1, n - 2) in safe):
                                    return False
                            seen.add((ni, nj))
                            newfire.append((ni, nj))
                fire = newfire[::]
                
                # We move then.
                newsafe = []
                for i, j in safe:
                    for di, dj in dirs:
                        ni, nj = i + di, j + dj
                        # If we can reach bot-right cell, success.
                        if ni == m - 1 and nj == n - 1:
                            return True
                        if 0 <= ni < m and 0 <= nj < n and A[ni][nj] == 0 and (ni, nj) not in seen:   
                            seen.add((ni, nj))
                            newsafe.append((ni, nj))
                safe = newsafe[::]
                
            # If there is no more cell for us to move before reaching bot-right cell, we fail.
            return False


        # check if always safe:
        while f:
            i, j = f.popleft()
            for di, dj in dirs:
                ni, nj = i + di, j + dj
                if 0 <= ni < m and 0 <= nj < n and A[ni][nj] == 0 and (ni, nj) not in seen:
                    seen.add((ni, nj))
                    f.append((ni, nj))
        f = collections.deque([(0, 0)])
        while f:
            i, j = f.popleft()
            for di, dj in dirs:
                ni, nj = i + di, j + dj
                if 0 <= ni < m and 0 <= nj < n and A[ni][nj] == 0 and (ni, nj) not in seen:
                    if ni == m - 1 and nj == n - 1:
                        return 10 ** 9 
                    seen.add((ni, nj))
                    f.append((ni, nj))


        # Binary search to find maximum days:
        l, r = 0, 10 ** 4
        while l < r:
            mid = (l + r + 1) // 2
            if helper(mid):
                l = mid
            else:
                r = mid - 1

  
      return l if helper(l) else -1

第二种方法:

Thanks to cza_wudi’s solution.

我们只需要 BFS 两遍,就可以找到人和火的最早到达右下角的时间,然后作比较就可以了。

第一排,计算人到安全区域的时间,是 9 次移动;

第二排,计算火到安全区域的最早时间,是 10 次移动。

有一种 人 和 火 赛跑的感觉有没有。

我们只能关注抵达最右下角的时间差。因为在从【左上->右下】这样方向确定的路线上,时间差是非增的。

参考下面这张图,假设我们比火领先 2 天到,就是说,火在两天后到达我们刚刚去过的地方。因为火只能延特定的路径走,所以我们在剩下的路线上都是领先 2 步的。所以我们可以用这个时间差来计算题目里说,我们可以在原地等多久。

要注意好 边缘案例:

如果我们不能抵达 安全点,返回 -1;

如果火到不了 安全点,返回 10**9;

我们提前 x 天到安全点,意味着,我们可以待 x 天,也可以待 x-1 天,取决于我们是否会被火紧跟着。如果火紧随其后,则意味着大火与人相同的道路。因此,如果时间差为 x 天,则在同一路径中,如果您延迟 x 天出发,您将在同一单元格中与大火相同,因此该人最多可以延迟(x -1)天,以确保该人至少领先一个格子。

相反,如果火势来自相反的方向,则不必担心火在相同的路径上,这意味着只有接触点,就在右下。

这种思路的代码:

def maximumMinutes(self, A: List[List[int]]) -> int:
        m, n = len(A), len(A[0])
        dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)]
        
        ppl_time = [[-1] * n for _ in range(m)]
        fire_time = [[-1] * n for _ in range(m)]
        
        # BFS for people's arrival for each cell.
        ppl_front = collections.deque([(0, 0, 0)])
        while ppl_front:
            cx, cy, days = ppl_front.popleft()
            ppl_time[cx][cy] = days
            for dx, dy in dirs:
                nx, ny = cx + dx, cy + dy
                if 0 <= nx < m and 0 <= ny < n and A[nx][ny] == 0 and ppl_time[nx][ny] == -1:
                    ppl_front.append((nx, ny, days + 1))
        
        
        # BFS for fire's arrival for each cell.
        fire_front = collections.deque([(x, y, 0) for x in range(m) for y in range(n) if A[x][y] == 1])
        while fire_front:
            cx, cy, days = fire_front.popleft()
            fire_time[cx][cy] = days
            for dx, dy in dirs:
                nx, ny = cx + dx, cy + dy
                if 0 <= nx < m and 0 <= ny < n and A[nx][ny] == 0 and fire_time[nx][ny] == -1:
                    fire_front.append((nx, ny, days + 1))
        
        # Check the arrival days for the bottom-right cell.
        ppl_arrival = ppl_time[-1][-1]
        fire_arrival = fire_time[-1][-1]
        
        # Some edge cases.
        if ppl_arrival == -1:
            return -1
        if fire_arrival == -1:
            return 10 ** 9
        if fire_arrival < ppl_arrival:
            return -1

        # Whether we are 'followed' by fire on both two pathes toward bot-right cell.
        diff = fire_arrival - ppl_arrival
        ppl_1, ppl_2 = ppl_time[-1][-2], ppl_time[-2][-1]
        
        fire_1, fire_2 = fire_time[-1][-2], fire_time[-2][-1]
        if ppl_1 > -1 and ppl_2 > -1 and (fire_1 - ppl_1 > diff or fire_2 - ppl_2 > diff):
            return diff
        return diff - 1


了解 小匚的个人博客 的更多信息

订阅后即可通过电子邮件收到最新文章。

了解 小匚的个人博客 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读

了解 小匚的个人博客 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读

退出移动版