vlambda博客
学习文章列表

【并查集 | 二分查找 + BFS】LeetCode 778. 水位上升的泳池中游泳

2021 第 14 篇题解

778. 水位上升的泳池中游泳


题目来源:力扣(LeetCode)https://leetcode-cn.com/problems/swim-in-rising-water/

题目


在一个 N x N 的坐标方格 grid 中,每一个方格的值 grid[i][j] 表示在位置 (i,j) 的平台高度。

现在开始下雨了。当时间为 t 时,此时雨水导致水池中任意位置的水位为 t 。你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。当然,在你游泳的时候你必须待在坐标方格里面。

你从坐标方格的左上平台 (0,0) 出发。最少耗时多久你才能到达坐标方格的右下平台 (N-1, N-1)

示例 1:

输入: [[0,2],[1,3]]
输出: 3
解释:
时间为0时,你位于坐标方格的位置为 (0, 0)。
此时你不能游向任意方向,因为四个相邻方向平台的高度都大于当前时间为 0 时的水位。

等时间到达 3 时,你才可以游向平台 (1, 1). 因为此时的水位是 3,坐标方格中的平台没有比水位 3 更高的,所以你可以游向坐标方格中的任意位置

示例2:

输入: [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]]
输出: 16
解释:
 0  1  2  3  4
24 23 22 21  5
12 13 14 15 16
11 17 18 19 20
10  9  8  7  6

最终的路线用加粗进行了标记。
我们必须等到时间为 16,此时才能保证平台 (0, 0) 和 (4, 4) 是连通的

提示:

  1. 2 <= N <= 50.
  2. grid[i][j][0, ..., N*N - 1] 的排列。

解题思路


思路:二分查找 + BFS,并查集

先审题,首先题目给定一个 的网格 (这里表示水池),其中每个方格中的值 表示的是水池位置 的平台高度。

现在题目给出的场景是这样的:⚡天下雨了☔,当时间为 时,雨水会导致水池中任意平台位置的水位都为 。题目允许从一个平台游向其他任意的平台,这里的前提是两个平台同时被雨水淹没。

其中在限定的网格中,假设可以瞬间移动无限距离,即是默认游动不耗时,问从网格左上方 出发,到达网格右下方 处最小耗时要多久?

首先先看下题意中需要注意的部分:

  • 时间为 时,雨水会导致水池中任意平台位置的水位为
  • 假设可以瞬间移动无限距离,在平台同时被雨水淹没时可以任意游动。

也就是说,题目要找到一个时刻 ,使得水池平台高度小于或等于 的部分,存在一条由左上角通往右下角的路径。

二分查找 + BFS

根据上面的题意可知,当 越大时,水池中平台可游动的部分越多;反之当 越小时,可游动的部分越小(因为平台高度较大的可能还未被淹没)。

题目最后的提示中说明 的排列。那么我们可以使用二分查找的方法在 中找得一个最短时间。具体方法如下:

  • 定义 分别指向 , 先取 中间值
  • 从左上角开始出发,向四个方向进行遍历访问(本题解用 BFS 方法,这里注意已经访问的进行标记):
    • 如果此时能够找到一条路径,从左上角到达右下角,那么 落在 处,可继续查找是否存在更小的时刻
    • 如果未能找到一条路径时,那么要找的 一定比此时的 值大,落在 中,往这个区间继续搜索。
  • 持续查找,缩小区间,最终即可求得答案时刻

具体的实现代码如下。

class Solution:
    def swimInWater(self, grid: List[List[int]]) -> int:
        directions = [(01), (10), (0-1), (-10)]
        n = len(grid)

        def bfs(threshold):
            queue = collections.deque()
            queue.append((00))


            # 注意标记已访问部分
            visited = [[False] * n for i in range(n)]
            visited[0][0] = True

            while queue:
                x, y = queue.popleft()
                # 向四个方向进行遍历访问
                for dx, dy in directions:
                    nx = x + dx
                    ny = y + dy
                    # 限定边界
                    # 同时防止重复访问
                    if 0 <= nx < n and 0 <= ny < n and not visited[nx][ny] and grid[nx][ny] <= threshold:
                        queue.append((nx, ny))
                        visited[nx][ny] = True
            
            return visited[-1][-1]
        

        left = 0
        right = n * n - 1
        
        while left < right:
            mid = left + (right - left) // 2
            # 注意判断 grid[0][0] <= mid
            # 如果大于 mid,那么说明所求答案一定是大于 mid 的
            if grid[0][0] <= mid and bfs(mid):
                right = mid
            else:
                left = mid + 1
        
        return left

并查集

这道题,我们也可以用并查集来解决。

我们将网格中的每个方格看出是图的顶点。因为时间 决定水池平台的水位,当考虑两个相邻的顶点时,只有平台高度小于或等于 时,才能够将两个顶点进行连通。

这里我们从小到大考虑时刻 ,每经过一个时刻,如果高度为 的方格相邻方格的高度都不大于 ,那么两个方格能够连通,对四个方位相邻的格子判断完之后,判断左上角与右下角方格是否连通。若能连通,那么此时的 即是答案,否则继续下个时刻

思路描述可能比较模糊,可直接看代码进行理解。

class UnionFind:
    def __init__(self, n):
        self.parent = [i for i in range(n)]
    
    def find(self, x):
        if x != self.parent[x]:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]
    
    def union(self, x, y):
        x_root = self.find(x)
        y_root = self.find(y)
        if x_root != y_root:
            self.parent[x_root] = y_root
    
    def connected(self, x, y):
        return self.find(x) == self.find(y)

class Solution:
    def swimInWater(self, grid: List[List[int]]) -> int:
        directions = [(01), (10), (0-1), (-10)]

        n = len(grid)

        # 因为需要从小到大考虑时刻 t,从平台高度 t 出发判断是否能与相邻的方块连通
        # 所以这里需要存储每个平台高度对应的位置
        idx = [0] * (n * n)
        for i in range(n):
            for j in range(n):
                idx[grid[i][j]] = (i, j)
        
        print(idx)
        uf = UnionFind(n * n)
        for t in range(n * n):
            # 对高度为 t 的平台进行判断是否能与相邻四个方位的平台连通
            x, y = idx[t]
            for dx, dy in directions:
                nx = x + dx
                ny = y + dy
                if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] <= t:
                    # 尝试合并相邻的平台
                    uf.union(x * n + y, nx * n + ny)
                    
            
            # 检查左下角与右下角是否连通,
            # 若能连通,此时的 t 即是答案
            if uf.connected(0, n * n - 1):
                return t
        
        
        return -1

欢迎关注



如有错误,烦请指出,欢迎指点交流。若觉得写得还不错,麻烦点个赞👍,谢谢。

- END -