给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
提示:
-
m == grid.length
-
n == grid[i].length
-
1 <= m, n <= 300
-
grid[i][j]
的值为 '0'
或 '1'
二、方法一
深度优先搜索
class Solution {
public int numIslands(char[][] grid) {
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
if (grid[i][j] == '1') {
res++;
dfs(i, j, grid);
}
}
}
return res;
}
public void dfs(int x, int y, char[][] grid) {
if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length || grid[x][y] != '1') {
return;
}
grid[x][y] = '2';
dfs(x + 1, y, grid);
dfs(x - 1, y, grid);
dfs(x, y + 1, grid);
dfs(x, y - 1, grid);
}
}
复杂度分析
- 时间复杂度:O(MN),其中 M 和 N 分别为行数和列数。
- 空间复杂度:O(MN),在最坏情况下,整个网格均为陆地,深度优先搜索的深度达到 MN。
三、方法二
广度优先搜索
class Solution {
public int numIslands(char[][] grid) {
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
if (grid[i][j] == '1') {
res++;
bfs(i, j, grid);
}
}
}
return res;
}
public void bfs(int x, int y, char[][] grid) {
Queue<int[]> queue = new LinkedList<>();
queue.add(new int[] {x, y});
while (!queue.isEmpty()) {
int[] pos = queue.poll();
int i = pos[0];
int j = pos[1];
if (i >= 0 && i < grid.length && j >= 0 && j < grid[0].length && grid[i][j] == '1') {
grid[i][j] = '2';
queue.add(new int[]{i + 1, j});
queue.add(new int[]{i - 1, j});
queue.add(new int[]{i, j + 1});
queue.add(new int[]{i, j - 1});
}
}
}
}
复杂度分析
- 时间复杂度:O(MN),其中 M 和 N 分别为行数和列数。
- 空间复杂度:O(min(M,N)),在最坏情况下,整个网格均为陆地,队列的大小可以达到 min(M,N)。
四、方法三
广度优先搜索一维坐标
class Solution {
int r;
int c;
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int res = 0;
r = grid.length;
c = grid[0].length;
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
if (grid[i][j] == '1') {
res++;
bfs(i, j, grid);
}
}
}
return res;
}
public void bfs(int x, int y, char[][] grid) {
Queue<Integer> queue = new LinkedList<>();
queue.add(x * c + y);
while (!queue.isEmpty()) {
int pos = queue.poll();
int i = pos / c ;
int j = pos % c;
if (i + 1 < r && grid[i + 1][j] == '1') {
grid[i + 1][j] = '2';
queue.add((i + 1) * c + j);
}
if (i - 1 >= 0 && grid[i - 1][j] == '1') {
grid[i - 1][j] = '2';
queue.add((i - 1) * c + j);
}
if (j + 1 < c && grid[i][j + 1] == '1') {
grid[i][j + 1] = '2';
queue.add(i * c + j + 1);
}
if (j - 1 >= 0 && grid[i][j - 1] == '1') {
grid[i][j - 1] = '2';
queue.add(i * c + j - 1);
}
}
}
}
复杂度分析
- 时间复杂度:O(MN),其中 M 和 N 分别为行数和列数。
- 空间复杂度:O(min(M,N)),在最坏情况下,整个网格均为陆地,队列的大小可以达到
五、方法四
并查集
class Solution {
class UnionFind{
int count;
int[] p;
int[] r;
public UnionFind(char[][] grid) {
count = 0;
int m = grid.length;
int n = grid[0].length;
p = new int[m * n];
r = new int[m * n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n ;j++) {
if (grid[i][j] == '1') {
p[i * n + j] = i * n + j;
++count;
}
r[i * n + j] = 0;
}
}
}
public int find(int i) {
if (p[i] != i) p[i] = find(p[i]);
return p[i];
}
public void union(int x, int y) {
int rootx = find(x);
int rooty = find(y);
if (rootx != rooty) {
if (r[rootx] > r[rooty]) {
p[rooty] = rootx;
} else if (r[rootx] < r[rooty]) {
p[rootx] = rooty;
} else {
p[rooty] = rootx;
r[rootx] += 1;
}
--count;
}
}
public int getCount() {
return count;
}
}
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int r = grid.length;
int c = grid[0].length;
UnionFind union = new UnionFind(grid);
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
if (grid[i][j] == '1') {
grid[i][j] = '2';
if (i + 1 < r && grid[i + 1][j] == '1'){
union.union(i * c + j, (i + 1) * c + j);
}
if (i - 1 >= 0 && grid[i - 1][j] == '1') {
union.union(i * c + j, (i - 1) * c + j);
}
if (j + 1 < c && grid[i][j + 1] == '1') {
union.union(i * c + j, i * c + j + 1);
}
if (j - 1 >= 0 && grid[i][j - 1] == '1') {
union.union(i * c + j, i * c + j - 1);
}
}
}
}
return union.getCount();
}
}
复杂度分析
- 时间复杂度:O(MN×α(MN)),其中 M 和 N 分别为行数和列数。注意当使用路径压缩(见 find 函数)和按秩合并(见数组 rank)实现并查集时,单次操作的时间复杂度α(MN),其中α(x) 为反阿克曼函数,当自变量 x 的值在人类可观测的范围内(宇宙中粒子的数量)时,函数α(x) 的值不会超过 5,因此也可以看成是常数时间复杂度。
- 空间复杂度:O(MN),这是并查集需要使用的空间。