题目
200. 岛屿数量
给你一个由 '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
解法一
思路:
图表深度优先搜索。
按照套路我们有以下几步,
- 申明一个Set对象visited,用于存储节点是否有被遍历过
- 申明一个count用来记录岛屿的数量。
- 遍历这个图表,如果某个节点值为1,则岛屿的数量+1,并且去递归的遍历它的上下左右,如果周围相邻节点未被遍历过,且值为1,则继续递归的遍历这个相邻节点的上下左右。
- 最终返回岛屿数量count。
代码如下:
/**
* @param {character[][]} grid
* @return {number}
*/
var numIslands = function(grid) {
if(!grid || grid.length ==0){
return 0;
}
let height = grid.length;
let width = grid[0].length;
let visited = [];
for(let i=0;i<height;i++){
visited[i] = []
}
let count = 0;
for(let i=0;i<height;i++){
for(let j=0;j<width;j++){
if(grid[i][j] == 1 && !visited[i][j]){
count++;
dfs(grid,visited,i,j);
}
}
}
return count;
};
function dfs(grid,visited,i,j){
let height = grid.length;
let width = grid[0].length;
if(i<0 || i>height-1||j<0||j>width-1||visited[i][j] || grid[i][j] == 0){
return ;
}
visited[i][j] = true;
dfs(grid,visited,i,j+1);
dfs(grid,visited,i,j-1);
dfs(grid,visited,i+1,j);
dfs(grid,visited,i-1,j);
}
复杂度分析
时间复杂度:O(M*N),M和N分别为矩阵的高和宽
空间复杂度:O(M*N),存放visted的数组为M*N,另外,当全部为陆地的时候,递归栈的最大高度为M*N。
ps:这里其实是可以优化的,不用visited,直接判断当前节点为0则不遍历,然后每遍历一个陆地之后,将它的值改为0即可。我是为了套一下图表深度优先遍历的模板,用一个visited,大家好理解。
解法二
思路:
广度优先遍历。
- 申明一个队列,用于存放节点值为1的相邻节点。
- 申明一个变量count,用于存放岛屿数量。
- 遍历grid每一个节点,如果节点值为1,将该节点推入队列,并且count+1。
- 将队列头节点弹出,如果该节点的上下左右有节点为1,则将该相邻节点推入队列。
- 只要队列不为空,就一直操作,并且没处理一个节点,都要记得把值改为0
- 最终count即位岛屿数量。
代码如下:
/**
* @param {character[][]} grid
* @return {number}
*/
var numIslands = function(grid) {
if(!grid || grid.length ==0){
return 0;
}
let height = grid.length;
let width = grid[0].length;
let count = 0;
let queue = [];
for(let i=0;i<height;i++){
for(let j=0;j<width;j++){
if(grid[i][j] == 1){
count++;
queue.push([i,j])
while(queue.length>0){
let node = queue.shift();
let p = node[0],q = node[1];
grid[p][q]=0;
offerToQueue(grid,queue,p-1,q);
offerToQueue(grid,queue,p+1,q);
offerToQueue(grid,queue,p,q-1);
offerToQueue(grid,queue,p,q+1);
}
}
}
}
return count;
};
function offerToQueue(grid,queue,i,j){
let height = grid.length;
let width = grid[0].length;
if(i<0 || j<0 || i>height-1||j>width-1 || grid[i][j] == 0){
return ;
}
queue.push([i,j]);
grid[i][j]=0;
}
复杂度分析:
时间复杂度:O(M*N),M和N分别为矩阵的高和宽。
空间复杂度:O(M*N),M和N分别为矩阵的高和宽。
解法三
思路:
并查集。
我们可以利用并查集,先将节点都处理一遍,构建初始化父子关系,并初始化连通量为陆地数量。然后遍历所有节点,如果当前节点为1,则去看它的上下左右节点,如果有1,就合并两个节点,并且连通量-1,最终返回联通量即可。
代码如下:
/**
* @param {character[][]} grid
* @return {number}
*/
var numIslands = function(grid) {
if(!grid || grid.length ==0){
return 0;
}
let height = grid.length;
let width = grid[0].length;
let count = 0;
let parent = new Map();
// 先处理parent,值为1的放入parent
for(let i=0;i<height;i++){
for(let j=0;j<width;j++){
if(grid[i][j] == 1){
let node = [i,j];
parent.set(`${i}_${j}`,node);
count++;
}
}
}
for(let i=0;i<height;i++){
for(let j=0;j<width;j++){
// 如果值为1,先将它自己的值设为0,再去找它的上下左右,如果找到了,就合并,并且count--
if(grid[i][j] == 1){
grid[i][j] = 0;
union(grid,parent,[i,j],[i,j+1])==1 && count--
union(grid,parent,[i,j],[i,j-1])==1 && count--
union(grid,parent,[i,j],[i-1,j])==1 && count--
union(grid,parent,[i,j],[i+1,j])==1 && count--
}
}
}
return count;
};
function union(grid,parent,p,q){
let height = grid.length;
let width = grid[0].length;
if(q[0]<0 || q[1]<0 || q[0]>height-1||q[1]>width-1 || grid[q[0]][q[1]] == 0){
return ;
}
let rootP = find(parent,p)
let rootQ = find(parent,q)
if(rootP != rootQ){
parent.set(`${rootP[0]}_${rootP[1]}`,rootQ)
return 1;
}
}
function find(parent,node){
if(parent.get(`${node[0]}_${node[1]}`) != node){
parent.set(`${node[0]}_${node[1]}`,find(parent,parent.get(`${node[0]}_${node[1]}`)))
}
return parent.get(`${node[0]}_${node[1]}`);
}
复杂度分析:
时间复杂度:O(MNlog(MN)),先要处理parent数组为MN,后续遍历grid,最坏情况全是岛屿,每个节点都需要遍历到为MN,此时find查找复杂度为logMN,故为O(MNlog(MN))
空间复杂度:O(M*N),最坏情况全是陆地,则需要parent需要存放所有的节点