Skip to content

Commit 618c312

Browse files
committed
200
1 parent 90470ac commit 618c312

File tree

3 files changed

+262
-3
lines changed

3 files changed

+262
-3
lines changed

SUMMARY.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
* [98. Validate Binary Search Tree](leetCode-98-Validate-Binary-Search-Tree.md)
103103
* [99. Recover Binary Search Tree](leetcode-99-Recover-Binary-Search-Tree.md)
104104
* [100. Same Tree](leetcode-100-Same-Tree.md)
105-
* [101 题到 199](leetcode-101-200.md)
105+
* [101 题到 200](leetcode-101-200.md)
106106
* [101. Symmetric Tree](leetcode-101-Symmetric-Tree.md)
107107
* [102. Binary Tree Level Order Traversal](leetcode-102-Binary-Tree-Level-Order-Traversal.md)
108108
* [103. Binary Tree Zigzag Level Order Traversal](leetcode-103-Binary-Tree-Zigzag-Level-Order-Traversal.md)
@@ -177,4 +177,5 @@
177177
* [190. Reverse Bits](leetcode-190-Reverse-Bits.md)
178178
* [191. Number of 1 Bits](leetcode-191-Number-of-1-Bits.md)
179179
* [198. House Robber](leetcode-198-House-Robber.md)
180-
* [199. Binary Tree Right Side View](leetcode-199-Binary-Tree-Right-Side-View.md)
180+
* [199. Binary Tree Right Side View](leetcode-199-Binary-Tree-Right-Side-View.md)
181+
* [200. Number of Islands](leetcode-200-Number-of-Islands.md)

leetcode-101-200.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,6 @@
146146

147147
<a href="leetcode-198-House-Robber.html">198. House Robber</a>
148148

149-
<a href="leetcode-199-Binary-Tree-Right-Side-View.html">199. Binary Tree Right Side View</a>
149+
<a href="leetcode-199-Binary-Tree-Right-Side-View.html">199. Binary Tree Right Side View</a>
150+
151+
<a href="leetcode-200-Number-of-Islands.html">200. Number of Islands</a>

leetcode-200-Number-of-Islands.md

+256
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# 题目描述(中等难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/200.jpg)
4+
5+
一个二维数组,把 `1` 看做陆地,把 `0` 看做大海,陆地相连组成一个岛屿。把数组以外的区域也看做是大海,问总共有多少个岛屿。
6+
7+
# 解法一
8+
9+
想法很简单,我们只需要遍历二维数组,然后遇到 `1` 的时候,把当前的 `1` 以及它周围的所有 `1` 都标记成一个字符,这里直接标记成 `2`。然后记录遇到了几次 `1`,就代表有几个岛屿。看下边的例子。
10+
11+
```java
12+
[1] 1 0 0 0
13+
1 1 0 0 0
14+
0 0 1 0 0
15+
0 0 0 1 1
16+
当前遇到了 1, count = 1;
17+
把当前的 1 和它周围的 1 标记为 2
18+
2 2 0 0 0
19+
2 2 0 0 0
20+
0 0 1 0 0
21+
0 0 0 1 1
22+
23+
2 2 0 0 0
24+
2 2 0 0 0
25+
0 0 [1] 0 0
26+
0 0 0 1 1
27+
遇到下一个 1, count = 2;
28+
把当前的 1 和它周围的 1 标记为 2
29+
2 2 0 0 0
30+
2 2 0 0 0
31+
0 0 2 0 0
32+
0 0 0 1 1
33+
34+
2 2 0 0 0
35+
2 2 0 0 0
36+
0 0 2 0 0
37+
0 0 0 [1] 1
38+
遇到下一个 1, count = 3;
39+
把当前的 1 和它周围的 1 标记为 2
40+
2 2 0 0 0
41+
2 2 0 0 0
42+
0 0 2 0 0
43+
0 0 0 2 2
44+
45+
没有 1 了,所以岛屿数是 count = 3 个。
46+
```
47+
48+
还有一个问题就是怎么标记与当前 `1` 相邻的 `1`。也很直接,我们直接把和当前 `1` 连通的位置看做一个图,然后做一个遍历即可。可以直接用递归写一个 `DFS`,即深度优先遍历。
49+
50+
```java
51+
public int numIslands(char[][] grid) {
52+
int count = 0;
53+
int rows = grid.length;
54+
if (rows == 0) {
55+
return 0;
56+
}
57+
int cols = grid[0].length;
58+
for (int r = 0; r < rows; r++) {
59+
for (int c = 0; c < cols; c++) {
60+
if (grid[r][c] == '1') {
61+
count++;
62+
marked(r, c, rows, cols, grid);
63+
}
64+
}
65+
}
66+
return count;
67+
}
68+
69+
private void marked(int r, int c, int rows, int cols, char[][] grid) {
70+
if (r == -1 || c == -1 || r == rows || c == cols || grid[r][c] != '1') {
71+
return;
72+
}
73+
//当前 1 标记为 2
74+
grid[r][c] = '2';
75+
76+
//向上下左右扩展
77+
marked(r + 1, c, rows, cols, grid);
78+
marked(r, c + 1, rows, cols, grid);
79+
marked(r - 1, c, rows, cols, grid);
80+
marked(r, c - 1, rows, cols, grid);
81+
82+
}
83+
```
84+
85+
当然做遍历的话,我们也可以采用 `BFS`,广度优先遍历。图的广度优先遍历和二叉树的 [层次遍历](https://leetcode.wang/leetcode-102-Binary-Tree-Level-Order-Traversal.html) 类似,只需要借助一个队列即可。
86+
87+
和上边的区别不大,改一下标记函数即可。
88+
89+
此外入队列的时候,我们把二维坐标转为了一维,就省去了再创建一个类表示坐标。
90+
91+
```java
92+
public int numIslands(char[][] grid) {
93+
int count = 0;
94+
int rows = grid.length;
95+
if (rows == 0) {
96+
return 0;
97+
}
98+
int cols = grid[0].length;
99+
for (int r = 0; r < rows; r++) {
100+
for (int c = 0; c < cols; c++) {
101+
if (grid[r][c] == '1') {
102+
count++;
103+
bfs(r, c, rows, cols, grid);
104+
}
105+
}
106+
}
107+
return count;
108+
}
109+
private void bfs(int r, int c, int rows, int cols, char[][] grid) {
110+
Queue<Integer> queue = new LinkedList<Integer>();
111+
queue.offer(r * cols + c);
112+
while (!queue.isEmpty()) {
113+
int cur = queue.poll();
114+
int row = cur / cols;
115+
int col = cur % cols;
116+
//已经标记过就结束,这句很关键,不然会把一些节点重复加入
117+
if(grid[row][col] == '2'){
118+
continue;
119+
}
120+
grid[row][col] = '2';
121+
//将上下左右连通的 1 加入队列
122+
if (row != (rows - 1) && grid[row + 1][col] == '1') {
123+
queue.offer((row + 1) * cols + col);
124+
}
125+
if (col != (cols - 1) && grid[row][col + 1] == '1') {
126+
queue.offer(row * cols + col + 1);
127+
}
128+
if (row != 0 && grid[row - 1][col] == '1') {
129+
queue.offer((row - 1) * cols + col);
130+
}
131+
if (col != 0 && grid[row][col - 1] == '1') {
132+
queue.offer(row * cols + col - 1);
133+
}
134+
135+
}
136+
}
137+
```
138+
139+
# 解法二 并查集
140+
141+
一开始看到这道题,我其实想到的是并查集,然后想了想感觉有些复杂,复杂度可能会高一些,就换了下思路想到了解法一。逛了一下 `Discuss` 发现也有人用并查集实现了,那这里也再总结下。
142+
143+
并查集在 [130 题](https://leetcode.wang/leetcode-130-Surrounded-Regions.html) 中用过一次,把当时的介绍在粘过来。
144+
145+
看下维基百科对 [并查集](<https://zh.wikipedia.org/wiki/%E5%B9%B6%E6%9F%A5%E9%9B%86>) 的定义。
146+
147+
> [计算机科学](https://zh.wikipedia.org/wiki/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)中,**并查集**是一种树型的[数据结构](https://zh.wikipedia.org/wiki/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84),用于处理一些[不交集](https://zh.wikipedia.org/wiki/%E4%B8%8D%E4%BA%A4%E9%9B%86)(Disjoint Sets)的合并及查询问题。有一个**联合-查找算法****union-find algorithm**)定义了两个用于此数据结构的操作:
148+
>
149+
> - Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
150+
> - Union:将两个子集合并成同一个集合。
151+
>
152+
> 由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(union-find data structure)或合并-查找集合(merge-find set)。其他的重要方法,MakeSet,用于创建单元素集合。有了这些方法,许多经典的[划分问题](https://zh.wikipedia.org/w/index.php?title=%E5%88%92%E5%88%86%E9%97%AE%E9%A2%98&action=edit&redlink=1)可以被解决。
153+
>
154+
> 为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数。
155+
156+
网上很多讲并查集的文章了,这里推荐 [一篇](<https://blog.csdn.net/liujian20150808/article/details/50848646>),大家可以先去看一下。
157+
158+
知道了并查集,下边就很好解决了,因为你会发现,我们做的就是分类的问题,把相邻的 `1` 都分成一类。
159+
160+
首先我们把每个节点各作为一类,用它的行数和列数生成一个 `id` 标识该类。
161+
162+
```java
163+
int node(int i, int j) {
164+
return i * cols + j;
165+
}
166+
```
167+
168+
`nums` 来记录当前有多少个岛屿,初始化的时候每个 `1` 都认为是一个岛屿,然后开始合并。
169+
170+
遍历每个为 `1 ` 的节点,将它的右边和下边的 `1` 和当前节点合并(这里算作一个优化,不需要像解法一那样上下左右)。每进行一次合并,我们就将 `nums``1`
171+
172+
最后返回 `nums` 即可。
173+
174+
```java
175+
class UnionFind {
176+
int[] parents;
177+
int nums;
178+
179+
public UnionFind(char[][] grid, int rows, int cols) {
180+
nums = 0;
181+
// 记录 1 的个数
182+
for (int i = 0; i < rows; i++) {
183+
for (int j = 0; j < cols; j++) {
184+
if (grid[i][j] == '1') {
185+
nums++;
186+
}
187+
}
188+
}
189+
190+
//每一个类初始化为它本身
191+
int totalNodes = rows * cols;
192+
parents = new int[totalNodes];
193+
for (int i = 0; i < totalNodes; i++) {
194+
parents[i] = i;
195+
}
196+
}
197+
198+
void union(int node1, int node2) {
199+
int root1 = find(node1);
200+
int root2 = find(node2);
201+
//发生合并,nums--
202+
if (root1 != root2) {
203+
parents[root2] = root1;
204+
nums--;
205+
}
206+
}
207+
208+
int find(int node) {
209+
while (parents[node] != node) {
210+
parents[node] = parents[parents[node]];
211+
node = parents[node];
212+
}
213+
return node;
214+
}
215+
216+
int getNums() {
217+
return nums;
218+
}
219+
}
220+
221+
int rows;
222+
int cols;
223+
224+
public int numIslands(char[][] grid) {
225+
if (grid.length == 0)
226+
return 0;
227+
228+
rows = grid.length;
229+
cols = grid[0].length;
230+
UnionFind uf = new UnionFind(grid, rows, cols);
231+
232+
for (int row = 0; row < rows; row++) {
233+
for (int col = 0; col < cols; col++) {
234+
if (grid[row][col] == '1') {
235+
// 将下边右边的 1 节点和当前节点合并
236+
if (row != (rows - 1) && grid[row + 1][col] == '1') {
237+
uf.union(node(row, col), node(row + 1, col));
238+
}
239+
if (col != (cols - 1) && grid[row][col + 1] == '1') {
240+
uf.union(node(row, col), node(row, col + 1));
241+
}
242+
}
243+
}
244+
}
245+
return uf.getNums();
246+
247+
}
248+
249+
int node(int i, int j) {
250+
return i * cols + j;
251+
}
252+
```
253+
254+
#
255+
256+
解法一标记的思想前边的题目也遇到过好多次了,解法二的话算作一个通用的解法,当发现题目是分类相关的,可以考虑并查集。

0 commit comments

Comments
 (0)