岛屿数量问题是LeetCode上经典的图论题目(编号200),也是面试中的高频考题。题目要求计算二维网格中岛屿的数量,其中'1'代表陆地,'0'代表水域。岛屿由水平或垂直方向上相邻的陆地组成,被水域完全包围。
这个问题的现实意义在于图像处理中的连通区域分析,比如卫星图像中的陆地识别、医学影像中的细胞团检测等。算法层面,它考察的是图的遍历能力,特别是深度优先搜索(DFS)和广度优先搜索(BFS)的应用。
关键理解:岛屿即连通分量(Connected Component),解题本质是统计矩阵中'1'的连通块数量。
最直观的解法是遍历整个矩阵,遇到'1'时进行标记并统计。但这种方法时间复杂度高达O(n^4),因为每个点都可能触发全矩阵扫描。我们需要更聪明的标记方式。
DFS是解决这类连通性问题的首选方案。当发现一个未被访问的'1'时,递归访问其上下左右的相邻节点,并标记为已访问。这样每个岛屿只会被计数一次。
python复制def numIslands(grid):
if not grid:
return 0
count = 0
rows, cols = len(grid), len(grid[0])
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
count += 1
dfs(grid, i, j)
return count
def dfs(grid, i, j):
if i<0 or j<0 or i>=len(grid) or j>=len(grid[0]) or grid[i][j] != '1':
return
grid[i][j] = '#' # 标记已访问
dfs(grid, i+1, j)
dfs(grid, i-1, j)
dfs(grid, i, j+1)
dfs(grid, i, j-1)
BFS使用队列实现,同样能有效解决该问题。对于每个新发现的'1',将其所有相邻'1'入队并标记。
python复制from collections import deque
def numIslands(grid):
if not grid:
return 0
count = 0
rows, cols = len(grid), len(grid[0])
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
count += 1
queue = deque([(i,j)])
while queue:
x, y = queue.popleft()
if 0<=x<rows and 0<=y<cols and grid[x][y] == '1':
grid[x][y] = '#'
queue.extend([(x+1,y), (x-1,y), (x,y+1), (x,y-1)])
return count
并查集特别适合处理动态连通性问题。我们将每个'1'视为独立集合,通过与相邻节点的合并操作最终统计集合数量。
python复制class UnionFind:
def __init__(self, grid):
rows, cols = len(grid), len(grid[0])
self.parent = [i for i in range(rows * cols)]
self.count = sum(grid[i][j] == '1' for i in range(rows) for j in range(cols))
def find(self, i):
if self.parent[i] != i:
self.parent[i] = self.find(self.parent[i])
return self.parent[i]
def union(self, x, y):
rootx = self.find(x)
rooty = self.find(y)
if rootx != rooty:
self.parent[rooty] = rootx
self.count -= 1
def numIslands(grid):
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
uf = UnionFind(grid)
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
for (x, y) in [(i+1,j), (i-1,j), (i,j+1), (i,j-1)]:
if 0<=x<rows and 0<=y<cols and grid[x][y] == '1':
uf.union(i*cols + j, x*cols + y)
return uf.count
类似题目(LeetCode 695)要求找出最大的岛屿面积。只需在DFS/BFS时增加计数器即可:
python复制def maxAreaOfIsland(grid):
max_area = 0
def dfs(i, j):
if 0<=i<len(grid) and 0<=j<len(grid[0]) and grid[i][j] == 1:
grid[i][j] = 0
return 1 + dfs(i+1,j) + dfs(i-1,j) + dfs(i,j+1) + dfs(i,j-1)
return 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == 1:
max_area = max(max_area, dfs(i,j))
return max_area
LeetCode 1254要求统计完全被水域包围的岛屿数量。需要在边界处理上特别处理:
python复制def closedIsland(grid):
count = 0
rows, cols = len(grid), len(grid[0])
def dfs(i, j):
if i<0 or j<0 or i>=rows or j>=cols:
return False
if grid[i][j] != 0:
return True
grid[i][j] = 1
res = True
for x,y in [(i+1,j),(i-1,j),(i,j+1),(i,j-1)]:
res &= dfs(x,y)
return res
for i in range(rows):
for j in range(cols):
if grid[i][j] == 0 and dfs(i,j):
count += 1
return count
更复杂的变种需要识别不同形状的岛屿(LeetCode 694)。可以通过记录DFS/BFS的遍历顺序来编码岛屿形状:
python复制def numDistinctIslands(grid):
shapes = set()
rows, cols = len(grid), len(grid[0])
def dfs(i, j, direction):
if 0<=i<rows and 0<=j<cols and grid[i][j] == 1:
grid[i][j] = 0
shape.append(direction)
dfs(i+1, j, 'D')
dfs(i-1, j, 'U')
dfs(i, j+1, 'R')
dfs(i, j-1, 'L')
shape.append('B') # 回溯标记
for i in range(rows):
for j in range(cols):
if grid[i][j] == 1:
shape = []
dfs(i, j, 'S') # 起始点
shapes.add(tuple(shape))
return len(shapes)
调试技巧:打印每次遍历后的矩阵状态,可视化观察算法执行过程。对于2x2或3x3的小矩阵,可以手工验证算法正确性。