200. 岛屿数量
LeetCode 2001 · 中等 · 图论
题
一张由 '1'(陆地)和 '0'(水)组成的网格,上下左右相连的陆地算同一座岛。数有几座岛。
例:
→ 3。
一句话
逐格扫,遇到陆地就岛数 +1,并从它出发 BFS 把整座岛改成水,之后扫到这座岛的其他格子就不会重复计数。
关键技巧
网格就是图。 每个格子是节点,上下左右相邻的陆地之间有边;一座岛就是一个连通分量。「数岛」就是「数连通分量」。
外层循环负责找新分量,内层遍历负责吞掉整个分量。 外层扫到一个还没访问过的陆地,它一定属于一座没数过的岛——如果那座岛数过了,它早就在那次遍历里被标记了。所以每次命中计数 +1,然后遍历把整座岛标记。
原地改 '0' 当 visited。 省一个 \(m \times n\) 的访问数组;不允许改输入时再另开。
入队时就标记,不是出队时。 出队才标记的话,一个格子可能被好几个邻居重复入队,最坏退化;入队即标记保证每格进队一次。
用 BFS / 显式栈,不用递归 DFS。 网格最大 \(300 \times 300\),一整片陆地的递归深度可达 9 万,Python 默认递归上限 1000,直接爆。
解
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
m, n = len(grid), len(grid[0])
count = 0
for i in range(m):
for j in range(n):
if grid[i][j] != "1":
continue
count += 1
grid[i][j] = "0" # 入队即标记
q = deque([(i, j)])
while q:
x, y = q.popleft()
for nx, ny in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
if 0 <= nx < m and 0 <= ny < n and grid[nx][ny] == "1":
grid[nx][ny] = "0"
q.append((nx, ny))
return count
时间 \(O(mn)\),空间 \(O(\min(m, n))\)(BFS 队列最宽处是一条对角线)。
延伸
- 并查集也能做:每块陆地和右、下邻居 union,最后数根的个数。数量不变时不如 BFS 直白,但岛屿动态增加时(305. 岛屿数量 II2)只能用它。
- 同一个 flood fill 模板的变体:695. 岛屿的最大面积3(遍历时计数)、130. 被围绕的区域4(从边界反向淹)。
- 从多个起点同时扩散、按层计时:994. 腐烂的橘子。
- 网格上的回溯搜索(要撤销标记):79. 单词搜索——那里标记是路径级的,这里是全局的,不撤销。
自测
s = Solution()
g = [list("11110"), list("11010"), list("11000"), list("00000")]
assert s.numIslands(g) == 1
g = [list("11000"), list("11000"), list("00100"), list("00011")]
assert s.numIslands(g) == 3
assert s.numIslands([["0"]]) == 0
assert s.numIslands([["1"]]) == 1
assert s.numIslands([list("10101")]) == 3
# 大片陆地,递归 DFS 会爆栈
assert s.numIslands([["1"] * 300 for _ in range(300)]) == 1
-
LeetCode 200,2026-09-29 读。 ↩
-
305. 岛屿数量 II,2026-09-29 读。 ↩
-
695. 岛屿的最大面积,2026-09-29 读。 ↩
-
130. 被围绕的区域,2026-09-29 读。 ↩