407. 接雨水 II : 经典 Dijkstra 运用题

简介: 407. 接雨水 II : 经典 Dijkstra 运用题

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 407. 接雨水 II ,难度为 困难


Tag : 「图论最短路」、「优先队列(堆)」


给你一个 m x nmxn 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。



示例 1:


网络异常,图片无法展示
|


输入: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
输出: 4
解释: 下雨后,雨水将会被上图蓝色的方块中。总的接雨水量为1+2+1=4。
复制代码


示例 2:


网络异常,图片无法展示
|


输入: heightMap = [[3,3,3,3,3],[3,2,2,2,3],[3,2,1,2,3],[3,2,2,2,3],[3,3,3,3,3]]
输出: 10
复制代码


提示:


  • m == heightMap.length
  • n == heightMap[i].length
  • 1 <= m, n <= 200
  • 0 <= heightMap[i][j] <= 2 * 10^40<=heightMap[i][j]<=2104


Dijkstra 变形 + 优先队列(堆)



首先,最外层的一圈(边界)是不会接到任何雨水的(会从边界流出)。


我们定义从点 (x, y)(x,y) 到边界的路径中出现的最大高度为「路径高度」,路径高度 hh 必然满足 h >= heightMap[x][y]h>=heightMap[x][y]


问题的本质是求「从点 (x, y)(x,y) 到边界的所有路径高度的最小值为多少」,这个路径高度的最小值与 (x, y)(x,y) 本身的高度 heightMap[x][y]heightMap[x][y] 之间的差值,即是该点能接到的雨水数量。


PS. 对此觉得不好理解的同学,可以尝试从现实角度出发进行考虑:假如该位置是出水口(能够源源不断的出水并往各个方向蔓延),那么该位置最终承载多少水,取决于所有路径高度中的最小值(水流最终会被该路径高度的最小值所拦截),即出水口最终形成的水量为「路径高度最小值」与「出水口起始高度」之间的差值。


我们考虑如何计算某个位置 (x, y)(x,y) 的所有路径高度中的最小值。


如果是求从 (x, y)(x,y) 出发的所有路径中,路径总和的最小值,那么可以直接使用 Dijkstra 等单源最短路算法来进行求解。


本题求的是所有路径中,路径高度的最小值,需要对 Dijkstra 进行变形。


首先「从 (x, y)(x,y) 出发到达边界的路径」也可看作「从边界到达点 (x, y)(x,y) 的路

径」,经过转换操作后,直接计算边界到点 (x, y)(x,y) 的路径是一个多源点问题。


我们可以考虑引入「超级源点」进行简化:超级源点与所有的边界格子连有一条权值为 00 的边,从而进一步将问题转化为「求从超级源点出发到达 (x, y)(x,y) 的路径高度的最小值」。


与求最短路的 Dijkstra 类似,我们可以将「使用出队元素更新邻点的松弛操作」等价「使用出队元素更新相邻格子的雨水量」。


如果我们能够保证被出队元素所更新的高度为最终高度(或者说出队元素的高度为最终高度),那么该做法的正确性就能被 Dijkstra 的正确性所保证。


我们知道,对于边界格子而言,由于其不能接到任何雨水(即最终高度为起始高度),因此它们可以先进行入队。


然后考虑如何出队并更新邻点可以确保正确性。


对于某个位置 (x, y)(x,y) 而言,根据「木桶原理」,其最终高度取决于四个方向的邻点的最终高度的最小值。


这引导我们使用优先队列(堆)来做:建立一个小根堆,存储 (x, y, h)(x,y,h) 三元组信息( hh 为 位置 (x, y)(x,y) 的最终高度),每次使用出队元素来更新邻点,由于是小根堆,可以确保出队元素是被更新的元素的最小高度的邻点,并且被更新元素会被更新为最终高度,然后将被更新元素进行入队,重复此过程,直到所有位置均被更新。


实现上,我们不需要真将超级源点建立出来,只需要起始将所有边界点放入优先队列即可。同时由于每个点只有被第一个出队元素所更新的高度为最终高度,我们还需要使用 visvis 数组来避免重复更新。


代码:


class Solution {
    public int trapRainWater(int[][] heightMap) {
        int m = heightMap.length, n = heightMap[0].length;
        PriorityQueue<int[]> q = new PriorityQueue<>((a,b)->a[2]-b[2]);
        boolean[][] vis = new boolean[m][n];
        for (int i = 0; i < n; i++) {
            q.add(new int[]{0, i, heightMap[0][i]});
            q.add(new int[]{m - 1, i, heightMap[m - 1][i]});
            vis[0][i] = vis[m - 1][i] = true;
        }
        for (int i = 1; i < m - 1; i++) {
            q.add(new int[]{i, 0, heightMap[i][0]});
            q.add(new int[]{i, n - 1, heightMap[i][n - 1]});
            vis[i][0] = vis[i][n - 1] = true;
        }
        int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
        int ans = 0;
        while (!q.isEmpty()) {
            int[] poll = q.poll();
            int x = poll[0], y = poll[1], h = poll[2];
            for (int[] d : dirs) {
                int nx = x + d[0], ny = y + d[1];
                if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
                if (vis[nx][ny]) continue;
                if (h > heightMap[nx][ny]) ans += h - heightMap[nx][ny];
                q.add(new int[]{nx, ny, Math.max(heightMap[nx][ny], h)});
                vis[nx][ny] = true;
            }
        }
        return ans;
    }
}
复制代码


  • 时间复杂度:所有的点均进出一次优先队列(堆),复杂度为 O((m * n)\log{(m * n)})O((mn)log(mn))
  • 空间复杂度:O(m * n)O(mn)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.407 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
7月前
|
容器
leetcode-407:接雨水 II
leetcode-407:接雨水 II
77 0
|
7月前
|
图计算 索引
leetcode-42:接雨水
leetcode-42:接雨水
61 0
|
算法 测试技术 图计算
|
算法 Android开发 索引
LeetCode 周赛上分之旅 #44 同余前缀和问题与经典倍增 LCA 算法
学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场 LeetCode 周赛的解题报告,一起体会上分之旅。
83 0
|
2月前
|
算法 图计算 C++
Leetcode第42题(接雨水)
这篇文章介绍了解决LeetCode第42题“接雨水”问题的不同算法,包括双指针法和单调栈法,并提供了C++的代码实现。
20 0
Leetcode第42题(接雨水)
|
6月前
|
算法 图计算
力扣经典150题第十六题:接雨水
力扣经典150题第十六题:接雨水
30 0
|
7月前
|
算法 测试技术 索引
【力扣】45.跳跃游戏Ⅱ
【力扣】45.跳跃游戏Ⅱ
|
算法 Java 测试技术
LeetCode 周赛上分之旅 #46 经典二分答案与质因数分解
学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场 LeetCode 周赛的解题报告,一起体会上分之旅。
69 0
LeetCode 周赛上分之旅 #46 经典二分答案与质因数分解
|
机器学习/深度学习 人工智能 移动开发
Acwing 1574. 接雨水
Acwing 1574. 接雨水
69 0