下面这题我刚开始一直以为是求图的连通分量的个数,弄了好久发现总是有问题,后来才发现不是连通分量的题型,连通分量求的是顶点的被分成多少块,下面这种题目是一个矩阵被分成多少块,两者不一样
200. 岛屿数量
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入: 11110 11010 11000 00000 输出: 1
示例 2:
输入: 11000 11000 00100 00011 输出: 3 解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。
思路一:dfs
我们可以将二维网格看成一个无向图,竖直或水平相邻的 1 之间有边相连。
为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 11 都会被重新标记为 00。
最终岛屿的数量就是我们进行深度优先搜索的次数。
1 class Solution { 2 3 int rows, cols; 4 // dfs遍历一个与(i,j)都相连的4个单元 5 void dfs(char[][]grid, int x, int y){ 6 // 把grid[x][y]标记为0,即使设置为已访问 7 grid[x][y] = '0'; 8 9 // 遍历四个方向的单元 10 if(x - 1 >= 0 && grid[x-1][y] == '1'){ 11 dfs(grid, x-1, y); 12 } 13 if(x + 1 < rows && grid[x+1][y] == '1'){ 14 dfs(grid, x+1, y); 15 } 16 17 if(y - 1 >= 0 && grid[x][y-1] == '1'){ 18 dfs(grid, x, y-1); 19 } 20 if(y + 1 < cols && grid[x][y+1] == '1'){ 21 dfs(grid, x, y+1); 22 } 23 } 24 25 26 public int numIslands(char[][] grid) { 27 if(grid == null || (grid != null && grid.length == 0)) 28 return 0; 29 30 rows = grid.length; 31 cols = grid[0].length; 32 33 int cnt = 0; 34 // dfs遍历一个连通分量 35 for(int i = 0; i < rows; i++){ 36 for(int j = 0; j < cols; j++){ 37 if(grid[i][j] == '1'){ 38 cnt++; 39 dfs(grid, i, j); 40 } 41 } 42 } 43 return cnt; 44 } 45 }
力扣测试时间为1ms, 空间为42.4MB
复杂度分析:
时间复杂度:numIslands()函数中的双重循环其实只有少部分会进入dfs进行递归,不进入递归的迭代相比于进入迭代的情况花费的时间是非常短的,所以主要看进入递归的那些位置,而对每个位置为‘1‘的单元都进行了一次dfs()递归遍历,无论是在双重循环还是在dfs()内部,所以dfs()的调用次数可以作为时间复杂度的一个评定指标,dfs()的调用次数等于‘1‘的个数,'1'的个数最多为N*M, 所以时间复杂度为O(M*N)
空间复杂度:当所有位置都是1的时候,递归的深度最大为M*N, 所以空间复杂度为O(M*N)
思路二:BFS
我们可以扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行广度优先搜索。在广度优先搜索的过程中,每次把位置加入队列后就把这个位置置为'0'
1 class Solution { 2 3 public int numIslands(char[][] grid) { 4 if(grid == null || (grid != null && grid.length == 0)) 5 return 0; 6 7 int rows = grid.length; 8 int cols = grid[0].length; 9 10 int cnt = 0; 11 12 for(int i = 0; i < rows; i++){ 13 for(int j = 0; j < cols; j++){ 14 if(grid[i][j] == '1'){ // 如果位置为1,利用BFS,搜索整个相连的所有单元 15 cnt++; 16 System.out.println(i + " " + j); 17 Queue<Pair<Integer, Integer>> queue = new LinkedList<>(); 18 Pair<Integer, Integer> pair = new Pair<>(i, j); 19 queue.offer(pair); 20 grid[i][j] = '0'; 21 while(!queue.isEmpty()){ 22 pair = queue.poll(); 23 int row = pair.getKey(); 24 int col = pair.getValue(); 25 // 把四个方向相连的单元都添加到队列中 26 if(row - 1 >= 0 && grid[row-1][col] == '1'){ 27 queue.offer(new Pair<>(row-1, col)); 28 grid[row-1][col] = '0'; 29 } 30 if(row + 1 < rows && grid[row+1][col] == '1'){ 31 queue.offer(new Pair<>(row+1, col)); 32 grid[row+1][col] = '0'; 33 } 34 if(col - 1 >= 0 && grid[row][col-1] == '1'){ 35 queue.offer(new Pair<>(row, col-1)); 36 grid[row][col-1] = '0'; 37 } 38 if(col + 1 < cols && grid[row][col+1] == '1'){ 39 queue.offer(new Pair<>(row, col+1)); 40 grid[row][col+1] = '0'; 41 } 42 System.out.println(queue.size()); 43 44 } 45 } 46 } 47 } 48 return cnt; 49 } 50 }
力扣测试时间为97ms, 空间为42.9MB, 太慢了
复杂度分析:
时间复杂度和思路一一样
空间复杂度:O(min(M,N)),在最坏情况下,整个网格均为陆地,队列的大小可以达到min(M, N)。
思路三:并查集
将位置为(i,j)的坐标映射为parent数组的i * cols + j下标的元素,这样每个位置都能一一映射一个parent[]元素,从而可以改变其父节点
1 // 并查集实现 2 class Solution { 3 class UnionFind{ 4 int rows; // 矩阵的行数 5 int cols; // 矩阵的列数 6 int[] parent; // 并查集数组 7 int[] rank; // 存储每个数字所在树的高度,有助于降低树的高度 8 int count = 0; // 记录岛屿数量 9 10 // 初始化parent[][]数组为下标本身,count值为1的个数 11 public UnionFind(char[][] grid){ 12 rows = grid.length; 13 cols = grid[0].length; 14 parent = new int[rows * cols]; 15 rank = new int[rows * cols]; 16 for(int i = 0; i < rows; i++){ 17 for(int j = 0; j < cols; j++){ 18 if(grid[i][j] == '1'){ 19 count++; 20 } 21 parent[cols * i + j] = cols * i + j; 22 rank[cols * i + j] = 0; 23 } 24 } 25 } 26 27 // 查找父节点 28 int findFather(int i){ 29 if(parent[i] != i){ 30 parent[i] = parent[parent[i]]; // 路径压缩 31 } 32 return parent[i]; 33 } 34 35 // 合并两个点 36 public void union(int x, int y){ 37 // 查找两个数字的父节点 38 int xfather = findFather(x); 39 int yfather = findFather(y); 40 41 // 如果不是同一个则合并,合并的时候矮的树挂到高的树上 42 if(xfather != yfather){ 43 if(rank[xfather] > rank[yfather]){ 44 parent[yfather] = xfather; 45 }else if(rank[xfather] < rank[yfather]){ 46 parent[xfather] = yfather; 47 }else{ 48 parent[xfather] = yfather; 49 rank[yfather]++; // 树的高度加一 50 } 51 count--; // 每合并两个结点,count就减一 52 } 53 54 } 55 56 int getCount(){ 57 return count; 58 } 59 } 60 61 62 public int numIslands(char[][] grid) { 63 if(grid == null || (grid != null && grid.length == 0)) 64 return 0; 65 66 int rows = grid.length; 67 int cols = grid[0].length; 68 69 UnionFind uf = new UnionFind(grid); 70 for(int i = 0; i < rows; i++){ 71 for(int j = 0; j < cols; j++){ // 如果为1,则连接所有为1的四周结点 72 if(grid[i][j] == '1'){ 73 grid[i][j] = '0'; 74 if(i - 1 >= 0 && grid[i - 1][j] == '1') 75 uf.union(i * cols + j, (i - 1) * cols + j); 76 if(i + 1 < rows && grid[i + 1][j] == '1') 77 uf.union(i * cols + j, (i + 1) * cols + j); 78 if(j - 1 >= 0 && grid[i][j - 1] == '1') 79 uf.union(i * cols + j, i * cols + j - 1); 80 if(j + 1 < cols && grid[i][j + 1] == '1') 81 uf.union(i * cols + j, i * cols + j + 1); 82 } 83 } 84 } 85 86 return uf.getCount(); 87 } 88 }
力扣测试时间为7ms, 空间为42.5MB
复杂度分析:
时间复杂度:取决于矩阵式中'1'的个数,所以时间复杂度为O(N*M)
空间复杂度:两个矩阵rank[]和parent[],数组长度为N*M, 所以空间复杂度为O(N*M);
思路参考:
https://leetcode-cn.com/problems/number-of-islands/solution/dao-yu-shu-liang-by-leetcode/