|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[1162. 地图分析](https://leetcode-cn.com/problems/as-far-from-land-as-possible/solution/gong-shui-san-xie-ru-he-shi-yong-duo-yua-vlea/)** ,难度为 **中等**。 |
| 4 | + |
| 5 | +Tag : 「图论 BFS」、「多源 BFS」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +你现在手里有一份大小为 $N * N$ 的 网格 $grid,ドル上面的每个 单元格 都用 0ドル$ 和 1ドル$ 标记好了。 |
| 10 | + |
| 11 | +其中 0ドル$ 代表海洋,1ドル$ 代表陆地,请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的。 |
| 12 | + |
| 13 | +我们这里说的距离是「曼哈顿距离」:$(x0, y0)$ 和 $(x1, y1)$ 这两个单元格之间的距离是 $|x0 - x1| + |y0 - y1|$。 |
| 14 | + |
| 15 | +如果网格上只有陆地或者海洋,请返回 $-1$。 |
| 16 | + |
| 17 | +示例 1: |
| 18 | + |
| 19 | +``` |
| 20 | +输入:[[1,0,1],[0,0,0],[1,0,1]] |
| 21 | + |
| 22 | +输出:2 |
| 23 | + |
| 24 | +解释:海洋单元格 (1, 1) 和所有陆地单元格之间的距离都达到最大,最大距离为 2。 |
| 25 | +``` |
| 26 | +示例 2: |
| 27 | + |
| 28 | +``` |
| 29 | +输入:[[1,0,0],[0,0,0],[0,0,0]] |
| 30 | + |
| 31 | +输出:4 |
| 32 | + |
| 33 | +解释:海洋单元格 (2, 2) 和所有陆地单元格之间的距离都达到最大,最大距离为 4。 |
| 34 | +``` |
| 35 | + |
| 36 | +提示: |
| 37 | +* 1 <= grid.length == grid[0].length <= 100 |
| 38 | +* grid[i][j] 不是 0ドル$ 就是 1ドル$ |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +### 单源 BFS |
| 43 | + |
| 44 | +通常我们使用 BFS 求最短路,都是针对如下场景:从特定的起点出发,求解到达特定终点的最短距离。 |
| 45 | + |
| 46 | +**这是一类特殊的「单源最短路」问题:本质是在一个边权为 1ドル$ 的图上,求从特定「源点」出发到达特定「汇点」的最短路径。** |
| 47 | + |
| 48 | +对于本题,如果套用「单源最短路」做法,我们需要对每个「海洋」位置做一次 BFS:求得每个「海洋」的最近陆地距离,然后在所有的距离中取 $max$ 作为答案。 |
| 49 | + |
| 50 | +单次 BFS 的最坏情况需要扫描完整个矩阵,复杂度为 $O(n^2)$。 |
| 51 | + |
| 52 | +同时,最多有 $n^2$ 个海洋区域需要做 BFS,因此这样的做法复杂度为 $O(n^4),ドル并且 $O(n^4)$ 可直接取满。 |
| 53 | + |
| 54 | +PS. 数据范围为 10ドル^2,ドル理论上是一定会超时,但本题数据较弱,Java 2021年06月28日 可过。 |
| 55 | + |
| 56 | +一些细节:为了方便,我们在使用哈希表记录距离时,将二维坐标 $(x, y)$ 转化为对应的一维下标 $idx = x * n + y$ 作为 key 进行存储。 |
| 57 | + |
| 58 | +代码: |
| 59 | +```Java [] |
| 60 | +class Solution { |
| 61 | + int n; |
| 62 | + int[][] grid; |
| 63 | + public int maxDistance(int[][] _grid) { |
| 64 | + grid = _grid; |
| 65 | + n = grid.length; |
| 66 | + int ans = -1; |
| 67 | + for (int i = 0; i < n; i++) { |
| 68 | + for (int j = 0; j < n; j++) { |
| 69 | + if (grid[i][j] == 0) { |
| 70 | + ans = Math.max(ans, bfs(i, j)); |
| 71 | + } |
| 72 | + } |
| 73 | + } |
| 74 | + return ans; |
| 75 | + } |
| 76 | + // 单次 BFS:求解海洋位置 (x,y) 最近的陆地距离 |
| 77 | + int bfs(int x, int y) { |
| 78 | + int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}}; |
| 79 | + Deque<int[]> d = new ArrayDeque<>(); |
| 80 | + Map<Integer, Integer> map = new HashMap<>(); |
| 81 | + d.addLast(new int[]{x, y}); |
| 82 | + map.put(x * n + y, 0); |
| 83 | + while (!d.isEmpty()) { |
| 84 | + int[] poll = d.pollFirst(); |
| 85 | + int dx = poll[0], dy = poll[1]; |
| 86 | + int step = map.get(dx * n + dy); |
| 87 | + if (grid[dx][dy] == 1) return step; |
| 88 | + for (int[] di : dirs) { |
| 89 | + int nx = dx + di[0], ny = dy + di[1]; |
| 90 | + if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue; |
| 91 | + int key = nx * n + ny; |
| 92 | + if (map.containsKey(key)) continue; |
| 93 | + d.addLast(new int[]{nx, ny}); |
| 94 | + map.put(key, step + 1); |
| 95 | + } |
| 96 | + } |
| 97 | + return -1; |
| 98 | + } |
| 99 | +} |
| 100 | +``` |
| 101 | +* 时间复杂度:$O(n^4)$ |
| 102 | +* 空间复杂度:$O(n^2)$ |
| 103 | + |
| 104 | +--- |
| 105 | + |
| 106 | +### 多源 BFS |
| 107 | + |
| 108 | +这其实还是道「多源 BFS」入门题。 |
| 109 | + |
| 110 | +**与「单源最短路」不同,「多源最短路」问题是求从「多个源点」到达「一个/多个汇点」的最短路径。** |
| 111 | + |
| 112 | +在实现上,最核心的搜索部分,「多源 BFS」与「单源 BFS」并无区别。 |
| 113 | + |
| 114 | +**并且通过建立「虚拟源点」的方式,我们可以「多源 BFS」转换回「单源 BFS」问题。** |
| 115 | + |
| 116 | +什么意思? |
| 117 | + |
| 118 | +以本题为例,题面要我们求每个「海洋」区域到最近的「陆地」区域的最大值。 |
| 119 | + |
| 120 | +我们可以将「源点/起点」和「汇点/终点」进行反转:**从每个「陆地」区域出发,多个「陆地」区域每次同时向往扩散一圈,每个「海洋」区域被首次覆盖时所对应的圈数,就是「海洋」区域距离最近的「陆地」区域的距离。** |
| 121 | + |
| 122 | + |
| 123 | + |
| 124 | +不过,这是如何与「单源 BFS」联系起来的呢? |
| 125 | + |
| 126 | +我们可以想象存在一个「虚拟源点」,其与所有「真实源点」(陆地)存在等权的边,那么任意「海洋」区域与「最近的陆地」区域的最短路等价于与「虚拟源点」的最短路: |
| 127 | + |
| 128 | + |
| 129 | + |
| 130 | +**实现上,我们并不需要真的将这个虚拟源点建立出来,只需要将所有的「真实源点」进行入队即可。** |
| 131 | + |
| 132 | +这个过程相当于从队列中弹出「虚拟源点」,并把它所能到点(真实源点)进行入队,然后再进行常规的 BFS 即可。 |
| 133 | + |
| 134 | +一些细节:实现上为了方便,在进行常规 BFS 时,如果一个「海洋」区域被访问到,说明其被离它「最近的陆地」覆盖到了,修改值为最小距离。这样我们只需要考虑那些值仍然为 0ドル$ 的「海洋」区域即可(代表尚未被更新)。 |
| 135 | + |
| 136 | +代码: |
| 137 | +```Java |
| 138 | +class Solution { |
| 139 | + public int maxDistance(int[][] grid) { |
| 140 | + int n = grid.length; |
| 141 | + Deque<int[]> d = new ArrayDeque<>(); |
| 142 | + Map<Integer, Integer> map = new HashMap<>(); |
| 143 | + for (int i = 0; i < n; i++) { |
| 144 | + for (int j = 0; j < n; j++) { |
| 145 | + if (grid[i][j] == 1) { |
| 146 | + d.add(new int[]{i, j}); |
| 147 | + map.put(i * n + j, 0); |
| 148 | + } |
| 149 | + } |
| 150 | + } |
| 151 | + int ans = -1; |
| 152 | + int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}}; |
| 153 | + while (!d.isEmpty()) { |
| 154 | + int[] poll = d.poll(); |
| 155 | + int dx = poll[0], dy = poll[1]; |
| 156 | + int step = map.get(dx * n + dy); |
| 157 | + for (int[] di : dirs) { |
| 158 | + int nx = dx + di[0], ny = dy + di[1]; |
| 159 | + if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue; |
| 160 | + if (grid[nx][ny] != 0) continue; |
| 161 | + grid[nx][ny] = step + 1; |
| 162 | + d.add(new int[]{nx, ny}); |
| 163 | + map.put(nx * n + ny, step + 1); |
| 164 | + ans = Math.max(ans, step + 1); |
| 165 | + } |
| 166 | + } |
| 167 | + return ans; |
| 168 | + } |
| 169 | +} |
| 170 | +``` |
| 171 | +* 时间复杂度:$O(n^2)$ |
| 172 | +* 空间复杂度:$O(n^2)$ |
| 173 | + |
| 174 | +--- |
| 175 | + |
| 176 | +### 总结 |
| 177 | + |
| 178 | +今天我们介绍了「多源 BFS」,通过建立「虚拟源点」,我们可以将其转化回「单源 BFS」问题。 |
| 179 | + |
| 180 | +实现上我们只需要将所有的「真实源点」进行入队,然后再进行 BFS 即可。 |
| 181 | + |
| 182 | +看起来两者区别不大,但其本质是通过源点/汇点转换,应用常规的 Flood Fill 将多次朴素 BFS 转化为一次 BFS,可以有效降低我们算法的时间复杂度。 |
| 183 | + |
| 184 | +--- |
| 185 | + |
| 186 | +### 最后 |
| 187 | + |
| 188 | +这是我们「刷穿 LeetCode」系列文章的第 `No.1162` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先将所有不带锁的题目刷完。 |
| 189 | + |
| 190 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 191 | + |
| 192 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 193 | + |
| 194 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
0 commit comments