题目:
Given a 2d grid map of '1’s (land) and '0’s (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.
Example 1:Input: 11110 11010 11000 00000 Output: 1
Example 2:
Input: 11000 11000 00100 00011 Output: 3
解释:
看到这种题目的第一想法是用并查集做,但是我没有用并查集解决过这类问题(只做过用并查集解决提供相连边的问题),这个问题其实用暴力dfs可以做出来,但是第二次做的时候发现之前写得dfs速度特别慢,原因是,我把判断是否退出的条件判断写在了进入程序之后,而不是先判断是否满足条件再做判断,事实上,对于递归调用,本身函数入栈出栈是非常耗时的,所以以后遇到递归的问题,一定要先判断是否满足条件在进行函数调用,不然时间会爆掉,对于二叉树的问题也同样适用!这种技巧在优化时间复杂度的时候是非常有用的,也解释了为什么和别人的代码思路一样但是时间却差那么多,同时需要注意的是,不要以为用 in range(n)写法简便就一直用这个来当做判断语句,直观来说感觉这个速度比直接用不等号要慢。
这里直接把访问过得元素用'0'
标记,比用一个visited数组来判断是否再遍历此位置更加优化!
本来的写法,python ,566ms:
class Solution(object):
def numIslands(self, grid):
"""
:type grid: List[List[str]]
:rtype: int
"""
if not grid:
return 0
m,n=len(grid),len(grid[0])
def dfs(r,c):
if r not in xrange(m) or c not in xrange(n) or grid[r][c]!="1":
return
else:
grid[r][c]='0'
#这四个dfs整体来看是bfs
dfs(r-1,c)
dfs(r+1,c)
dfs(r,c-1)
dfs(r,c+1)
result=0
for r in xrange(m):
for c in xrange(n):
if grid[r][c]=='1':
dfs(r,c)
result+=1
return result
优化以后的写法(主要是优化dfs中条件的判断),56ms(用range当做判断语句的话,348ms还是明显比这个慢):
class Solution(object):
def numIslands(self, grid):
"""
:type grid: List[List[str]]
:rtype: int
"""
if not grid:
return 0
m,n=len(grid),len(grid[0])
def dfs(r,c):
grid[r][c]='0'
if r+1<m and grid[r+1][c]=='1':
dfs(r+1,c)
if r-1 >=0 and grid[r-1][c]=='1':
dfs(r-1,c)
if c+1<n and grid[r][c+1]=='1':
dfs(r,c+1)
if c-1>=0 and grid[r][c-1]=='1':
dfs(r,c-1)
result=0
for r in xrange(m):
for c in xrange(n):
if grid[r][c]=='1':
dfs(r,c)
result+=1
return result
从4个方向的角度精简写法,但是不知道为什么速度竟然变慢了:
class Solution(object):
def numIslands(self, grid):
"""
:type grid: List[List[str]]
:rtype: int
"""
if not grid:
return 0
m,n=len(grid),len(grid[0])
self.x=[-1,1,0,0]
self.y=[0,0,1,-1]
def dfs(r,c):
grid[r][c]='0'
for i in range(4):
new_r=r+self.x[i]
new_c=c+self.y[i]
if 0<=new_r<m and 0<=new_c<n and grid[new_r][new_c]=="1":
dfs(new_r,new_c)
result=0
for r in xrange(m):
for c in xrange(n):
if grid[r][c]=='1':
dfs(r,c)
result+=1
return result
用并查集来做:
并查集,如果一个点为“1”,那这个点和它左方上方为“1”的点属于一个联通块,并到一个集合就可以了。
如果一个点为“0”,那么把它的父节点置为-1。
最后统计出现的不为“-1”的不同数字的个数就可以了。
python代码:
class Solution(object):
def numIslands(self, grid):
"""
:type grid: List[List[str]]
:rtype: int
"""
if not grid:
return 0;
#Tree用于储存父节点
#最后统计出现的不为“-1”的不同数字的个数就可以了。
#要和左边上边的元素保持一致,是因为左边上边的元素已经更新过了
m,n=len(grid),len(grid[0])
Tree=[i for i in range(m*n)]
def findRoot(x):
if x!=Tree[x]:
Tree[x]=findRoot(Tree[x])
return Tree[x]
for i in range(m):
for j in range(n):
x=i*n+j
if grid[i][j]=='0':
Tree[x]=-1
else:
#上边的元素
if i-1>=0 and grid[i-1][j]=='1':
y=(i-1)*n+j
#合并
Tree[findRoot(x)]=findRoot(y)
#左边的元素
if j-1>=0 and grid[i][j-1]=='1':
y=i*n+j-1
Tree[findRoot(x)]=findRoot(y)
_set=set()
for i in range(m*n):
if Tree[i]!=-1:
y=findRoot(i)
_set.add(y)
return len(_set)
c++代码:
class Solution {
public:
vector<int> Tree;
int numIslands(vector<vector<char>>& grid) {
int m=grid.size();
if (m==0)
return 0;
int n=grid[0].size();
for (int i=0;i<m*n;i++)
Tree.push_back(i);
for (int i=0;i<m;i++)
{
for (int j=0;j<n;j++)
{
int x=i*n+j;
if(grid[i][j]=='0')
Tree[x]=-1;
else
{
if (i-1>=0 && grid[i-1][j]=='1')
{
int y=(i-1)*n+j;
Tree[findRoot(x)]=findRoot(y);
}
if (j-1>=0 && grid[i][j-1]=='1')
{
int y=i*n+(j-1);
Tree[findRoot(x)]=findRoot(y);
}
}
}
}
set<int> _set;
for(int i=0;i<m*n;i++)
{
if (Tree[i]!=-1)
{ //需要这一步的原因是,有的元素可能没有左边的元素和右边的元素,但是它也和别人联通
int y=findRoot(i);
_set.insert(y);
}
}
return _set.size();
}
int findRoot(int x)
{
if (x!=Tree[x])
Tree[x]=findRoot(Tree[x]);
return Tree[x];
}
};
总结:
有时候不要图写法上面的简便,算法最重要的还是考虑时间复杂度的问题,有时候虽然时间复杂度是一样的,但是用不同的方法实现,时间是不一样的。
能用改元素的方式标记就不要用visited
数组标记。