【图论刷题-4】力扣 778. 水位上升的泳池中游泳

图论刷题

  1. 机器人的运动范围
  2. 矩阵中的路径
  3. 图像渲染
  4. 水位上升的泳池中游泳

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

力扣原题 地址

难度与标签

困难难度

  • 深度优先遍历
  • 广度优先遍历
  • 并查找

题目描述

在一个 n x n 的整数矩阵 grid 中,每一个方格的值 grid[i][j] 表示位置 (i, j) 的平台高度。

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

你从坐标方格的左上平台 (0,0) 出发。返回 你到达坐标方格的右下平台 (n-1, n-1) 所需的最少时间 。

示例 1:
在这里插入图片描述

输入: grid = [[0,2],[1,3]]
输出: 3
解释:
时间为0时,你位于坐标方格的位置为 (0, 0)。
此时你不能游向任意方向,因为四个相邻方向平台的高度都大于当前时间为 0 时的水位。
等时间到达 3 时,你才可以游向平台 (1, 1). 因为此时的水位是 3,坐标方格中的平台没有比水位 3 更高的,所以你可以游向坐标方格中的任意位置

示例 2:

在这里插入图片描述

输入: grid = [[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
解释: 最终的路线用加粗进行了标记。
我们必须等到时间为 16,此时才能保证平台 (0, 0)(4, 4) 是连通的

提示:

  • n == grid.length
  • n == grid[i].length
  • 1 <= n <= 50
  • 0 <= grid[i][j] < n^2
  • grid[i][j] 中每个值 均无重复

题目分析

题目很有意思,但确实很难。首先简单的理解场景可以假设我们一只鸭子,要从 [0, 0] 游回家 [n-1, n-1]。注意是 回家,所以水要超过高台才表示这条路是通的,不然水比台子低不能游过去。

现在结合图1,一定要等水位到了 3 才可以通行。

接着我们给出我们读题目的重要结论:

  1. 假设 W = Max(grid) ,即 W 是二维数组最大的那个数,则等水位到了 W 一定有通路,可爱的小鸭子一定能回家。
  2. 我们的答案一定在 [0, W] 之间。

所以这个题目转换成寻找一个阈值 threshold ∈ [ 0 , W ] \in [0, W] [0,W] ,使得,当水位到 threshold 时,可以得到至少一条从 [0, 0][n-1, n-1] 回家的路。

我们拥有一个二维数组 ary,尺寸为 n x n,默认全部为 0.

步骤如下:

  1. 假设阈值为 Q,对二维数组 ary 进行对应位置的赋值操作,具体规则为:
     如果 grid[i][j] <= 阈值 Q,则 ary[i][j] = 1,表示这个点可以过。 
    
  2. 遍历 ary,查找是否存在通路 [0, 0][n-1, n-1]

很明显,如果 Q 越大,则可以通行的点就越多。

代码实现 1—— DFS + 二分查询

  1. 初始化 visited 数组,visited[i][j]true 表示已经访问或者不能访问。
  2. 编写一个DFS函数,遍历所有路径。如果能够达到目的地(回家)就返回 true;
  3. 二分查找阈值,每次根据阈值初始化 visited 数组,然后调用 DFS 函数检查是否可以访问。

重要补充grid[0][0] 位置不一定为 0,所以输出的结果 target 一定大于等于 grid[0][0]
,因此二分查找时起点为 grid[0][0],根据题目条件最大值也小于 n*n,所以二分查找的终点是 n*n-1

class Solution {
public:
    const int dx[4] = {0, 1, 0, -1};
    const int dy[4] = {1, 0, -1, 0};
    /**
     * 初始化 visited 数组,把不符合条件的点置于 true
     */
    void initVisited(vector<vector<int>> grid,
                     vector<vector<bool>>& visited,
                     int threshold) {
        int n = grid.size();
        for (int i=0; i<n; i++) {
            for (int j=0; j<n; j++) {
                visited[i][j] = grid[i][j] > threshold;
            }
        }
    }
    /**
     * dfs 遍历
     */
    bool dfs(vector<vector<bool>>& visited,
             int x, int y) {
        int n = visited.size();
        // 1. 判断是否到达终点
        if (x == n-1 && y == n-1) {
            return true;
        }
        // 2. 标记已经访问
        visited[x][y] = true;
        
        // 3. 深度优先遍历
        for (int i=0; i<4; i++) {
            int mx = x + dx[i];
            int my = y + dy[i];
            if (mx >= 0 && mx < n && my >= 0 && my < n && !visited[mx][my] ) {
                // 如果到达终点才 return 否则 continue for循环
                if (dfs(visited, mx, my)) {
                    return true;
                }
            }
        }
        return false;
    }

    int swimInWater(vector<vector<int>>& grid) {
        int n = grid.size();
        int left = grid[0][0];
        int right = n*n-1;
        vector<vector<bool>> visited(n, vector<bool>(n));

        while (left < right) {
            int mid = (left+right)/2;
            initVisited(grid, visited, mid);
            if (dfs(visited, 0, 0)) {
                right = mid;
            } else {
                left = mid+1;
            }
        }
        // left == right
        return left;
    }
};
  • 时间复杂度: O ( N 2 log ⁡ N ) O(N^2 \log N) O(N2logN)
  • 空间复杂度: O ( N 2 ) O(N^2) O(N2)

补充说明:如果题目中没有强调每个点的取值范围,那么可以首先找到最大的和最小的,再进行二分查询,一样的效果,不影响总体时间。

另外,这道题并不是最短路径问题,关注的是有多少水的时候小鸭子可以通行,因此,即便最后可能存在很多条路径,我们只需要确保有一条可以同行即可。

总结

跟官方的题解基本上是一样的,但是算法1中自己的想法是把额外条件附加在 visited 数组上, dfs 基本上没有什么变化。

如果理解了这道题目的意思,唯一容易出错的应该是 二分法查找阈值。事实上我是先通过所有测试用例并提示说超时,看了官方源码才想起用二分法查找的。

总而言之是一道有意思的题目,千万不能被 困难 两个字唬住了。

Smileyan
2022.3.22 17:08

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

smile-yan

感谢您的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值