题目描述
解法一:DFS
染色法,扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0。最终岛屿的数量就是我们进行深度优先搜索的次数。
class Solution {
public int numIslands(char[][] grid) {
if(grid.length == 0 || grid[0].length == 0) return 0;
int rows = grid.length;
int cols = grid[0].length;
int nums = 0;
for(int i=0; i<rows; i++) {
for(int j=0; j<cols; j++) {
// 如果一个位置为 1,则以其为起始节点开始进行深度优先搜索,其周围每个搜索到的 1 都会被重新标记为 0
if(grid[i][j] == '1') {
dfs(grid, i, j);
nums++;
}
}
}
return nums;
}
public void dfs(char[][] grid, int i, int j) {
// 如果索引越界,或者当前格子上为‘0’,则直接跳过
if(i<0 || i>= grid.length || j<0 || j>= grid[0].length || grid[i][j] == '0')
return;
grid[i][j] = '0';
dfs(grid, i-1, j); // 向上递归搜索
dfs(grid, i+1, j); // 向下递归搜索
dfs(grid, i, j-1); // 向左递归搜索
dfs(grid, i, j+1); // 向右递归搜索
}
}
复杂度分析:
- 时间复杂度:O(MN),其中 M 和 N 分别为行数和列数。
- 空间复杂度:O(MN),在最坏情况下,整个网格均为陆地,深度优先搜索的深度达到 M *M。
解法二:BFS
扫描整个二维网格。如果一个位置为 1,则将其加入队列,开始进行广度优先搜索。在广度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0。直到队列为空,搜索结束。最终岛屿的数量就是我们进行广度优先搜索的次数。
在写 “广度优先遍历” 的时候,要注意一点:
所有加入队列的结点,都应该马上(在入队的时候而不是出队的时候)被标记为 “已经访问”,反正只要进入了队列,你迟早都会遍历到它,否则,如果是等到出队列的时候再标记,会造成很多重复的结点进入队列,造成重复的操作。
class Solution {
public int numIslands(char[][] grid) {
if (grid.length == 0 || grid[0].length == 0) return 0;
int rows = grid.length, cols = grid[0].length;
boolean[][] visited = new boolean[rows][cols];
int count = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
// 如果某个点为陆地,并且没有被访问过, 则从该点开始进行广度优先遍历
if (!visited[i][j] && grid[i][j] == '1') {
count++;
Queue<Integer> q = new LinkedList<> ();
// 小技巧:把二维坐标转换为一个数字,否则,得用一个数组存
q.offer(i * cols + j);
// 注意:在入队的时候就马上要标记该点已经访问过
visited[i][j] = true;
while (!q.isEmpty()) {
int cur = q.poll(); // 如果队列不为空,则队首元素出队
// 从队列中存放的数字转换回矩阵中的坐标
int curX = cur / cols;
int curY = cur % cols;
// 将上方的陆地节点入队
if(curX-1 >= 0 && grid[curX-1][curY] == '1' && !visited[curX-1][curY]){ // 判断索引是否越界,是否为陆地,是否已访问过
q.offer((curX-1)*cols + curY);
visited[curX-1][curY] = true; // 放入队列以后,要马上标记成已经访问过
}
// 将下方的陆地节点入队
if(curX+1 < rows && grid[curX+1][curY] == '1' && !visited[curX+1][curY]){
q.offer((curX+1)*cols + curY);
visited[curX+1][curY] = true;
}
// 将左方的陆地节点入队
if(curY-1 >= 0 && grid[curX][curY-1] == '1' && !visited[curX][curY-1]){
q.offer(curX*cols + (curY-1));
visited[curX][curY-1] = true;
}
// 将右方的陆地节点入队
if(curY+1 < cols && grid[curX][curY+1] == '1' && !visited[curX][curY+1]){
q.offer(curX*cols + (curY+1));
visited[curX][curY+1] = true;
}
}
}
}
}
return count;
}
}
另一种写法
class Solution {
public int numIslands(char[][] grid) {
int count = 0;
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[0].length; j++) {
if(grid[i][j] == '1'){
bfs(grid, i, j);
count++;
}
}
}
return count;
}
private void bfs(char[][] grid, int i, int j){
Queue<int[]> list = new LinkedList<>(); // 队列中存放的是二维坐标(x,y)
list.add(new int[] { i, j });
while(!list.isEmpty()){
int[] cur = list.remove();
i = cur[0]; j = cur[1];
if(0 <= i && i < grid.length && 0 <= j && j < grid[0].length && grid[i][j] == '1') {
grid[i][j] = '0'; // 已经访问过的陆地节点就置为 0
list.add(new int[] { i + 1, j });
list.add(new int[] { i - 1, j });
list.add(new int[] { i, j + 1 });
list.add(new int[] { i, j - 1 });
}
}
}
}
复杂度分析:
- 时间复杂度:O(MN),其中 M 和 N 分别为行数和列数。
- 空间复杂度:O(min(M,N)),在最坏情况下,整个网格均为陆地,队列的大小达到min(M, N),因为bfs在二维矩阵中是斜着扫描的,当矩阵全为 1时,从左上向右下遍历,最长路径为对角线上的格子数目。
参考
https://leetcode-cn.com/problems/number-of-islands/solution/dfs-bfs-bing-cha-ji-python-dai-ma-java-dai-ma-by-l/
https://leetcode-cn.com/problems/number-of-islands/solution/number-of-islands-shen-du-you-xian-bian-li-dfs-or-/
https://leetcode-cn.com/problems/number-of-islands/solution/dao-yu-shu-liang-by-leetcode/