题目链接:
- 解题思路很明确,就是挨个遍历每个坐标,从每个坐标向周围扩散,将能遍历到的节点都遍历到,能执行多少次dfs/bfs就表示有多少个岛屿。
解法1:DFS
class Solution {
int[][] dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int res = 0;
public int numIslands(char[][] grid) {
int m = grid.length;
int n = grid[0].length;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
dfs(grid, i, j);
res++;
}
}
}
return res;
}
void dfs(char[][] grid, int row, int col) {
if (!inGrid(grid, row, col)) {
return;
}
if (grid[row][col] != '1') {
return;
}
grid[row][col] = '2';
int m = grid.length;
int n = grid[0].length;
for (int i = 0; i < 4; i++) {
int nextRow = row + dir[i][0];
int nextCol = col + dir[i][1];
dfs(grid, nextRow, nextCol);
}
}
boolean inGrid(char[][] grid, int row, int col) {
int m = grid.length;
int n = grid[0].length;
return row >= 0 && row < m && col >= 0 && col < n;
}
}
解法2:BFS
- BFS方式我们可以模拟二叉树bfs的写法,但是注意一点,二叉树bfs的时候我们一般是在队列出队的时候进行元素的访问,但是图里面我们必须在入队的时候就进行访问,举个例子说明一下:
[a, b]
[c, d]
对于这样一个grid,假设我们在a出队时才访问,我们访问完a后,队列中会有c, b,访问c后,那么d也会被加入队列,但由于d还没被访问,所以此时d的状态依旧是可访问的状态,那么下次访问b的时候会再次把d加入队列,这样就会出现同一个节点访问多次的情况,因此我们应该在进入队列的时候就对元素进行访问。而二叉树不会出现这样的情况在于处于同一层的的节点的孩子节点是没有关联的,不会出现这样的访问情况。
class Solution {
int[][] dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int res = 0;
public int numIslands(char[][] grid) {
int m = grid.length;
int n = grid[0].length;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
bfs(grid, i, j);
res++;
}
}
}
return res;
}
// bfs方式遍历
void bfs(char[][] grid, int row, int col) {
// 越界
if (!inGrid(grid, row, col)) {
return;
}
// 已经访问/无法访问(水)
if (grid[row][col] != '1') return;
// 此时表明(row, col)已经可以访问
// 采用bfs方式进行遍历
Deque<int[]> que = new ArrayDeque<>();
que.add(new int[]{row, col});
grid[row][col] = '2'; // 坐标加入队列的时候进行遍历访问
while (!que.isEmpty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
int[] temp = que.poll();
for (int j = 0; j < 4; j++) {
int nextRow = temp[0] + dir[j][0];
int nextCol = temp[1] + dir[j][1];
if (inGrid(grid, nextRow, nextCol) && grid[nextRow][nextCol] == '1') {
// 如果下一个访问的节点符合访问要求,直接进行访问
grid[nextRow][nextCol] = '2';
que.add(new int[]{nextRow, nextCol});
}
}
}
}
}
boolean inGrid(char[][] grid, int row, int col) {
int m = grid.length;
int n = grid[0].length;
return row >= 0 && row < m && col >= 0 && col < n;
}
}
- 补充:
由于之前写bfs版本是对照着二叉树的层序遍历去写的,所以也把size加入了其中,但二叉树层序遍历引入size的原因是为了记录每一层的节点,而在这里是不需要的所以我们可以去掉size,如下:
// bfs方式遍历
void bfs(char[][] grid, int row, int col) {
// 越界
if (!inGrid(grid, row, col)) {
return;
}
// 已经访问/无法访问(水)
if (grid[row][col] != '1') return;
// 此时表明(row, col)已经可以访问
// 采用bfs方式进行遍历
Deque<int[]> que = new ArrayDeque<>();
que.add(new int[]{row, col});
grid[row][col] = '2'; // 坐标加入队列的时候进行遍历访问
while (!que.isEmpty()) {
int[] temp = que.poll();
for (int j = 0; j < 4; j++) {
int nextRow = temp[0] + dir[j][0];
int nextCol = temp[1] + dir[j][1];
if (inGrid(grid, nextRow, nextCol) && grid[nextRow][nextCol] == '1') {
// 如果下一个访问的节点符合访问要求,直接进行访问
grid[nextRow][nextCol] = '2';
que.add(new int[]{nextRow, nextCol});
}
}
}
}
解法3:
本题是判断哪些岛屿属于同一块儿,其实也可以用并查集的方式解决,求出最终的连通分量个数,但需要注意由于水是不连通的,因此最终的并查集中包括岛屿 + 水的个数,所以还需要记录水的数量,最终用连通分量总数 减去水的个数即为岛屿个数.
class Solution {
/**
并查集是用来统计连通分量的,那么我们在dfs过程中,遍历到一个节点就进行union,那么最终直接通过连通分量
个数也能得到最终的答案
1 0
0 1
*/
int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public int numIslands(char[][] grid) {
int m = grid.length;
int n = grid[0].length;
// 由于本题中水是不能连通的,因此需要将连通分量中减去最终水的数量
int water = 0;
UnionFind uf = new UnionFind(m * n);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '0'){ water++; // 记录水的个数
continue;
}
dfs(grid, i, j, uf);
}
}
return uf.count() - water;
}
// dfs方式遍历节点,将能遍历到节点加入到一个连通分量中
void dfs(char[][] grid, int row, int col, UnionFind uf) {
int m = grid.length;
int n = grid[0].length;
grid[row][col] = '2';
for (int[] dir : dirs) {
int nextRow = dir[0] + row;
int nextCol = dir[1] + col;
// 没有越界,并且节点还未被访问,那么直接将其加入到当前的连通分量
if (inGrid(grid, nextRow, nextCol) && grid[nextRow][nextCol] == '1') {
uf.union(row * n + col, nextRow * n + nextCol);
dfs(grid, nextRow, nextCol, uf);
}
}
}
boolean inGrid(char[][] grid, int row, int col) {
int m = grid.length;
int n = grid[0].length;
return row >= 0 && row < m && col >= 0 && col < n;
}
class UnionFind {
private int count; // 连通分量个数
private int[] parent; // 记录每个节点的父节点,父节点为自身的是根节点
private int[] size; // 每个连通分量的大小,只需要记录根节点即可,其余节点无需记录
public UnionFind(int n) {
this.count = n;
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
public int count() {
return this.count;
}
// 查询节点的根节点
public int find(int p) {
int root = p;
while (root != parent[root]) {
root = parent[root];
}
// 路径压缩
while (p != root) {
int next = parent[p];
parent[p] = root;
p = next;
}
return root;
}
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
// 合并
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
if (size[rootP] > size[rootQ]) {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
} else {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
count--;
}
}
}
- 上面的代码总感觉怪怪的,当时写的时候不太理解并查集,我是用一个dfs去将所有连通分量加入到并查集中,但其实并不需要这样做,我们可以直接在main中遍历每一个节点,若当前节点为岛屿的一块儿,那么我们直接把四周的岛屿进行合并即可,这样遍历完成之后,所有岛屿都合并了
class Solution {
/**
并查集是用来统计连通分量的,那么我们在dfs过程中,遍历到一个节点就进行union,那么最终直接通过连通分量
个数也能得到最终的答案
1 0
0 1
*/
int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public int numIslands(char[][] grid) {
int m = grid.length;
int n = grid[0].length;
// 由于本题中水是不能连通的,因此需要将连通分量中减去最终水的数量
int water = 0;
UnionFind uf = new UnionFind(m * n);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '0'){
water++; // 记录水的个数
continue;
}
for (int[] dir : dirs) {
int x = i + dir[0], y = j + dir[1];
if (x < 0 || x >= m || y < 0 || y >= n || grid[x][y] == '0') {
continue;
}
uf.union(i * n + j, x * n + y);
}
}
}
return uf.count() - water;
}
class UnionFind {
private int count; // 连通分量个数
private int[] parent; // 记录每个节点的父节点,父节点为自身的是根节点
private int[] size; // 每个连通分量的大小,只需要记录根节点即可,其余节点无需记录
public UnionFind(int n) {
this.count = n;
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
public int count() {
return this.count;
}
// 查询节点的根节点
public int find(int p) {
int root = p;
while (root != parent[root]) {
root = parent[root];
}
// 路径压缩
while (p != root) {
int next = parent[p];
parent[p] = root;
p = next;
}
return root;
}
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
// 合并
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
if (size[rootP] > size[rootQ]) {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
} else {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
count--;
}
}
}
- 还有另外一种方式,就是并查集得到最终的集合中包含了岛屿以及一块儿块儿水,那么我们只需遍历一下所有节点,遇到水直接跳过,遇到陆地节点,查看其根节点是否出现过,若未出现,岛屿数量+1,并且由于我们的并查集经过了路径压缩,所以查询根节点也是常数级别时间复杂度的。
class Solution {
/**
并查集是用来统计连通分量的,那么我们在dfs过程中,遍历到一个节点就进行union,那么最终直接通过连通分量
个数也能得到最终的答案
1 0
0 1
*/
int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public int numIslands(char[][] grid) {
int m = grid.length;
int n = grid[0].length;
// 由于本题中水是不能连通的,因此需要将连通分量中减去最终水的数量
int water = 0;
UnionFind uf = new UnionFind(m * n);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '0'){
water++; // 记录水的个数
continue;
}
for (int[] dir : dirs) {
int x = i + dir[0], y = j + dir[1];
if (x < 0 || x >= m || y < 0 || y >= n || grid[x][y] == '0') {
continue;
}
uf.union(i * n + j, x * n + y);
}
}
}
Set<Integer> set = new HashSet<>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '0') continue;
int root = uf.find(i * n + j);
set.add(root);
}
}
return set.size();
}
class UnionFind {
private int count; // 连通分量个数
private int[] parent; // 记录每个节点的父节点,父节点为自身的是根节点
private int[] size; // 每个连通分量的大小,只需要记录根节点即可,其余节点无需记录
public UnionFind(int n) {
this.count = n;
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
public int count() {
return this.count;
}
// 查询节点的根节点
public int find(int p) {
int root = p;
while (root != parent[root]) {
root = parent[root];
}
// 路径压缩
while (p != root) {
int next = parent[p];
parent[p] = root;
p = next;
}
return root;
}
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
// 合并
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
if (size[rootP] > size[rootQ]) {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
} else {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
count--;
}
}
}