|
| 1 | +## 题目地址 (959. 由斜杠划分区域) |
| 2 | + |
| 3 | +https://leetcode-cn.com/problems/regions-cut-by-slashes/ |
| 4 | + |
| 5 | +## 题目描述 |
| 6 | + |
| 7 | +``` |
| 8 | +在由 1 x 1 方格组成的 N x N 网格 grid 中,每个 1 x 1 方块由 /、\ 或空格构成。这些字符会将方块划分为一些共边的区域。 |
| 9 | +
|
| 10 | +(请注意,反斜杠字符是转义的,因此 \ 用 "\\" 表示。)。 |
| 11 | +
|
| 12 | +返回区域的数目。 |
| 13 | +
|
| 14 | + |
| 15 | +
|
| 16 | +示例 1: |
| 17 | +
|
| 18 | +输入: |
| 19 | +[ |
| 20 | + " /", |
| 21 | + "/ " |
| 22 | +] |
| 23 | +输出:2 |
| 24 | +解释:2x2 网格如下: |
| 25 | +
|
| 26 | +示例 2: |
| 27 | +
|
| 28 | +输入: |
| 29 | +[ |
| 30 | + " /", |
| 31 | + " " |
| 32 | +] |
| 33 | +输出:1 |
| 34 | +解释:2x2 网格如下: |
| 35 | +
|
| 36 | +示例 3: |
| 37 | +
|
| 38 | +输入: |
| 39 | +[ |
| 40 | + "\\/", |
| 41 | + "/\\" |
| 42 | +] |
| 43 | +输出:4 |
| 44 | +解释:(回想一下,因为 \ 字符是转义的,所以 "\\/" 表示 \/,而 "/\\" 表示 /\。) |
| 45 | +2x2 网格如下: |
| 46 | +
|
| 47 | +示例 4: |
| 48 | +
|
| 49 | +输入: |
| 50 | +[ |
| 51 | + "/\\", |
| 52 | + "\\/" |
| 53 | +] |
| 54 | +输出:5 |
| 55 | +解释:(回想一下,因为 \ 字符是转义的,所以 "/\\" 表示 /\,而 "\\/" 表示 \/。) |
| 56 | +2x2 网格如下: |
| 57 | +
|
| 58 | +示例 5: |
| 59 | +
|
| 60 | +输入: |
| 61 | +[ |
| 62 | + "//", |
| 63 | + "/ " |
| 64 | +] |
| 65 | +输出:3 |
| 66 | +解释:2x2 网格如下: |
| 67 | +
|
| 68 | + |
| 69 | +
|
| 70 | +提示: |
| 71 | +
|
| 72 | +1 <= grid.length == grid[0].length <= 30 |
| 73 | +grid[i][j] 是 '/'、'\'、或 ' '。 |
| 74 | +
|
| 75 | +``` |
| 76 | + |
| 77 | +## 前置知识 |
| 78 | + |
| 79 | +- BFS |
| 80 | +- [DFS](https://github.com/azl397985856/leetcode/blob/master/thinkings/DFS.md "DFS") |
| 81 | +- [并查集](https://github.com/azl397985856/leetcode/blob/master/thinkings/union-find.md "并查集") |
| 82 | + |
| 83 | +## 公司 |
| 84 | + |
| 85 | +- 暂无 |
| 86 | + |
| 87 | +## 并查集 |
| 88 | + |
| 89 | +题目给了一个网格,网格有三个符号,分别是左斜杠,右斜杠和空格。我们要做的就是根据这些符号,将网格分成若干区域,并求区域的个数。 |
| 90 | + |
| 91 | +了解了题目之后,我们发现这其实就是一个求联通域个数的题目。这种题目一般有三种解法:**BFS**,**DFS** 和并查集。而如果题目需要求具体的联通信息,则需要使用 BFS 或 DFS 来完成。这里给大家提供 DFS 和并查集两种做法。 |
| 92 | + |
| 93 | +### 思路 |
| 94 | + |
| 95 | +使用并查集可以将网格按照如下方式进行逻辑上的划分,之所以进行如下划分的原因是一个网格最多只能被分成如下四个部分,而并查集的处理过程是**合并**,因此初始状态需要是一个个孤立的点,每一个点初始都是一个独立的联通区域。这在我下方代码的初始化过程有所体现。 |
| 96 | + |
| 97 | + |
| 98 | + |
| 99 | +> 编号方式无所谓,你可以按照你的喜好编号。不过编号方式改变了,代码要做相应微调。 |
| 100 | +
|
| 101 | +这里我直接使用了**不带权并查集模板** UF,没有改任何代码。 |
| 102 | + |
| 103 | +> 并查集模板在我的刷题插件中,插件可在我的公众号《力扣加加》回复插件获取 |
| 104 | +
|
| 105 | +而一般的并查集处理信息都是一维的,本题却是二维的,如何存储?实际上很简单,我们只需要做一个简单的数学映射即可。 |
| 106 | + |
| 107 | +```py |
| 108 | + |
| 109 | +def get_pos(row, col): |
| 110 | + return row * n + col |
| 111 | +``` |
| 112 | + |
| 113 | +如上代码会将原始格子 grid 的 grid[row][col] 映射到新的格子的一维坐标 `row * n + col`,其中 n 为列宽。而由于我们将一个格子拆成了四个,因此需要一个新的大网格来记录这些信息。而原始网格其实和旧的网格一一映射可确定,因此可以直接用原始网格,而不必新建一个新的大网格。如何做呢?其实将上面的坐标转换代码稍微修改就可以了。 |
| 114 | + |
| 115 | +```py |
| 116 | + |
| 117 | +def get_pos(row, col, i): |
| 118 | + return row * n + col + i |
| 119 | +``` |
| 120 | + |
| 121 | +接下来就是并查集的部分了: |
| 122 | + |
| 123 | +- 如果是 '/',则将 0 和 1 合并,2 和 3 合并。 |
| 124 | +- 如果是 '\\',则将 0 和 2 合并,1 和 3 合并。 |
| 125 | +- 如果是 ' ',则将 0, 1, 2, 3 合并。 |
| 126 | + |
| 127 | +最终返回联通区域的个数即可。 |
| 128 | + |
| 129 | +需要特别注意的是当前格子可能和原始格子的上面,下面,左面和右面的格子联通。因此不能仅仅考虑上面的格子内部的联通,还需要考虑相邻的格子的联通。为了避免**重复计算**,我们不能考虑四个方向,而是只能考虑两个方向,这里我考虑了上面和左面。 |
| 130 | + |
| 131 | +### 代码 |
| 132 | + |
| 133 | +代码支持: Python3 |
| 134 | + |
| 135 | +Python Code: |
| 136 | + |
| 137 | +```python |
| 138 | + |
| 139 | + |
| 140 | +class UF: |
| 141 | + def __init__(self, M): |
| 142 | + self.parent = {} |
| 143 | + self.cnt = 0 |
| 144 | + # 初始化 parent,size 和 cnt |
| 145 | + for i in range(M): |
| 146 | + self.parent[i] = i |
| 147 | + self.cnt += 1 |
| 148 | + |
| 149 | + def find(self, x): |
| 150 | + if x != self.parent[x]: |
| 151 | + self.parent[x] = self.find(self.parent[x]) |
| 152 | + return self.parent[x] |
| 153 | + return x |
| 154 | + def union(self, p, q): |
| 155 | + if self.connected(p, q): return |
| 156 | + leader_p = self.find(p) |
| 157 | + leader_q = self.find(q) |
| 158 | + self.parent[leader_p] = leader_q |
| 159 | + self.cnt -= 1 |
| 160 | + def connected(self, p, q): |
| 161 | + return self.find(p) == self.find(q) |
| 162 | + |
| 163 | +class Solution: |
| 164 | + def regionsBySlashes(self, grid): |
| 165 | + n = len(grid) |
| 166 | + N = n * n * 4 |
| 167 | + uf = UF(N) |
| 168 | + def get_pos(row, col, i): |
| 169 | + return (row * n + col) * 4 + i |
| 170 | + for row in range(n): |
| 171 | + for col in range(n): |
| 172 | + v = grid[row][col] |
| 173 | + if row > 0: |
| 174 | + uf.union(get_pos(row - 1, col, 2), get_pos(row, col, 1)) |
| 175 | + if col > 0: |
| 176 | + uf.union(get_pos(row, col - 1, 3), get_pos(row, col, 0)) |
| 177 | + if v == '/': |
| 178 | + uf.union(get_pos(row, col, 0), get_pos(row, col, 1)) |
| 179 | + uf.union(get_pos(row, col, 2), get_pos(row, col, 3)) |
| 180 | + if v == '\\': |
| 181 | + uf.union(get_pos(row, col, 1), get_pos(row, col, 3)) |
| 182 | + uf.union(get_pos(row, col, 0), get_pos(row, col, 2)) |
| 183 | + if v == ' ': |
| 184 | + uf.union(get_pos(row, col, 0), get_pos(row, col, 1)) |
| 185 | + uf.union(get_pos(row, col, 1), get_pos(row, col, 2)) |
| 186 | + uf.union(get_pos(row, col, 2), get_pos(row, col, 3)) |
| 187 | + |
| 188 | + return uf.cnt |
| 189 | +``` |
| 190 | + |
| 191 | +**复杂度分析** |
| 192 | + |
| 193 | +令 n 为网格的边长。 |
| 194 | + |
| 195 | +- 时间复杂度:$$O(n^2)$$ |
| 196 | +- 空间复杂度:$$O(n^2)$$ |
| 197 | + |
| 198 | +## DFS |
| 199 | + |
| 200 | +### 思路 |
| 201 | + |
| 202 | +要使用 DFS 在二维网格计算联通区域,我们需要对数据进行预处理。如果不明白为什么,可以看下我之前写的[小岛专题](https://github.com/azl397985856/leetcode/blob/master/thinkings/island.md "小岛专题")。 |
| 203 | + |
| 204 | +由于题目是“/” 和 "\\" 将联通区域进行了分割。因此我们可以将 “/” 和 "\\" 看成是陆地,其他部分看成是水。因此我们的目标就转化为小岛问题中的求水的区域个数。 |
| 205 | + |
| 206 | +至此,我们的预处理逻辑就清楚了。就是将题目中的“/” 和 "\\" 改成 1,其他空格改成 0,然后从 0 启动搜索(可以是 BFS 或者 DFS),边搜索边将水变成陆地,最终启动搜索的次数就是水的区域个数。 |
| 207 | + |
| 208 | +将 “/” 和 "\\" 直接变为 1 是肯定不行的。那将 “/” 和 "\\" 变成一个 2 X 2 的格子呢?也是不行的,因为无法处理上面提到的相邻格子的联通情况。 |
| 209 | + |
| 210 | +因此我们需要将 “/” 和 "\\" 变成一个 3 X 3 的格子。 |
| 211 | + |
| 212 | +> 4 X 4 以及更多的格子也是可以的,但没有必要了,那样只会徒增时间和空间。 |
| 213 | +
|
| 214 | + |
| 215 | + |
| 216 | +### 代码 |
| 217 | + |
| 218 | +代码支持: Python3 |
| 219 | + |
| 220 | +Python Code: |
| 221 | + |
| 222 | +```python |
| 223 | +class Solution: |
| 224 | + def regionsBySlashes(self, grid: List[str]) -> int: |
| 225 | + m, n = len(grid), len(grid[0]) |
| 226 | + new_grid = [[0 for _ in range(3 * n)] for _ in range(3 * m)] |
| 227 | + ans = 0 |
| 228 | + for i in range(m): |
| 229 | + for j in range(n): |
| 230 | + if grid[i][j] == '/': |
| 231 | + new_grid[3 * i][3 * j + 2] = 1 |
| 232 | + new_grid[3 * i + 1][3 * j + 1] = 1 |
| 233 | + new_grid[3 * i + 2][3 * j] = 1 |
| 234 | + if grid[i][j] == '\\': |
| 235 | + new_grid[3 * i][3 * j] = 1 |
| 236 | + new_grid[3 * i + 1][3 * j + 1] = 1 |
| 237 | + new_grid[3 * i + 2][3 * j + 2] = 1 |
| 238 | + def dfs(i, j): |
| 239 | + if 0 <= i < 3 * m and 0 <= j < 3 * n and new_grid[i][j] == 0: |
| 240 | + new_grid[i][j] = 1 |
| 241 | + dfs(i + 1, j) |
| 242 | + dfs(i - 1, j) |
| 243 | + dfs(i, j + 1) |
| 244 | + dfs(i, j - 1) |
| 245 | + for i in range(3 * m): |
| 246 | + for j in range(3 * n): |
| 247 | + if new_grid[i][j] == 0: |
| 248 | + ans += 1 |
| 249 | + dfs(i, j) |
| 250 | + return ans |
| 251 | +``` |
| 252 | + |
| 253 | +**复杂度分析** |
| 254 | + |
| 255 | +令 n 为网格的边长。 |
| 256 | + |
| 257 | +- 时间复杂度:虽然我们在 $$9 * m * n$$ 的网格中嵌套了 dfs,但由于每个格子最多只会被处理一次,因此时间复杂度仍然是 $$O(n^2)$$ |
| 258 | +- 空间复杂度:主要是 new_grid 的空间,因此空间复杂度是 $$O(n^2)$$ |
| 259 | + |
| 260 | +## 扩展 |
| 261 | + |
| 262 | +这道题的 BFS 解法留给大家来完成。 |
| 263 | + |
| 264 | +更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。 |
| 265 | + |
| 266 | +关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。 |
| 267 | + |
| 268 | + |
0 commit comments