求岛屿数量。
未标记
考查重点:连通分量计数与访问标记。遍历网格遇未访问陆地就计一个岛,并 DFS/BFS 标记整块四向连通区域;后续跳过已访格
本题目录
参考答案
核心回答
把陆地格子看作图的节点,上下左右相邻的陆地之间有边,岛屿数量就是陆地连通分量的数量。
遍历整个网格,每遇到一块尚未访问的陆地,就将岛屿数加一,并用 BFS 或 DFS 标记与它连通的全部陆地。后续扫描再遇到这些格子时直接跳过,因此每座岛只计数一次。
下面采用迭代 BFS,避免大岛屿造成递归调用过深。对于 m 行 n 列网格,时间复杂度为 O(mn),使用访问数组和队列时,额外空间复杂度为 O(mn)。
输入约定与例子
按力扣 200 的常见约定,输入是矩形字符网格,'1' 表示陆地,'0' 表示水。只计算上下左右四邻接,对角线相接不算连通;位于网格边缘的陆地仍然可以构成岛屿。以下实现还将空网格、零列网格的结果定义为 0。
例如:
1 1 0 0 1
1 0 0 0 1
0 0 1 0 0
左上角、右侧和底部中间分别形成一座岛,结果为 3。注意底部的单格不能因为与其他陆地“看起来很近”就并入同一座岛,必须存在四邻接的陆地路径。
Python 实现:迭代 BFS
这版使用独立的 visited,保留原网格内容。
from collections import deque
def num_islands(grid: list[list[str]]) -> int:
if not grid or not grid[0]:
return 0
rows, cols = len(grid), len(grid[0])
visited = [[False] * cols for _ in range(rows)]
directions = ((1, 0), (-1, 0), (0, 1), (0, -1))
islands = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] != "1" or visited[r][c]:
continue
islands += 1
visited[r][c] = True
queue = deque([(r, c)])
while queue:
x, y = queue.popleft()
for dx, dy in directions:
nx, ny = x + dx, y + dy
if (
0 <= nx < rows
and 0 <= ny < cols
and grid[nx][ny] == "1"
and not visited[nx][ny]
):
visited[nx][ny] = True
queue.append((nx, ny))
return islands
调用示例:
grid = [list("11001"), list("10001"), list("00100")]
print(num_islands(grid)) # 3
如果输入使用整数 1、0,将判断中的 "1" 改为 1,不要把字符与整数混用。实现按矩形网格约定访问;不规则行长度需要另行定义输入规则。
为什么在入队时标记
一个格子可能被多个相邻格子发现。如果等到出队才标记,它尚在队列中时,其他邻居还会把它再次加入。这里在首次发现并入队时就设置 visited,保证每块陆地最多入队一次。
Python 使用 deque.popleft() 取队首,避免 list.pop(0) 搬移后续元素。队列不设置 maxlen,否则容量满时可能丢弃尚未处理的格子。
正确性与复杂度
一次 BFS 从陆地出发,只沿相邻陆地扩展,因此不会跨水域走到另一座岛;另一方面,凡是与起点存在陆地路径的格子,都会随着路径上的节点被依次发现。所以这次搜索恰好标记起点所在的一整座岛。
扫描时,已统计岛屿的全部陆地都已标记,不能再次触发计数;未统计的岛屿至少有一个格子会被扫描到,并触发一次搜索。因此计数既不重复,也不遗漏。
外层扫描检查 mn 个格子。每块陆地最多入队、出队一次,最多检查四个方向,因此总时间为 O(mn),不是“外层遍历 × 每次搜索整张图”。
visited 占 O(mn);队列最坏也可用 O(mn) 作为上界,合计额外空间为 O(mn)。迭代遍历避免的是递归深度限制,并不意味着不需要存储待访问节点。
可以修改原图时怎样处理
如果允许改变输入,可以在发现陆地时直接将 '1' 改成 '0',利用原网格记录访问状态,省去独立 visited。修改同样要在首次入队时完成。
这种方式会破坏原始数据,调用方后续再读取网格时看到的是修改后的结果。如果先复制网格再修改,复制本身仍需要 O(mn) 空间;即使直接修改原图,BFS 队列也有额外空间,不能将整套解法写成 O(1)。
常见追问
怎样改成迭代 DFS?
将 queue = deque([(r, c)]) 改为 queue = [(r, c)],将 queue.popleft() 改为 queue.pop(),其余“发现即标记、加入待访问容器”的逻辑保持不变,就变成显式栈遍历。求连通分量不依赖按层顺序,因此两者得到相同计数。
如果斜对角也算连通呢?
把方向数组扩展为八个方向,加入四个对角方向。计数和标记机制不变。例如只有左上、右下两块陆地的 2×2 网格,四邻接答案为 2,八邻接答案为 1。
能否只扫描每格的左边和上边来直接计数?
只根据是否存在左、上陆地决定加一,可能把之后连起来的区域重复计数。如果采用并查集,则可以只检查左、上两条已经出现的边,但需要真正合并不同集合并维护连通分量数。单次静态计数通常用 BFS 或 DFS 更直接。
参考资料
- 国内:代码随想录:岛屿数量的广度优先搜索:借鉴未访问陆地触发计数及入队即标记,说明正文中避免重复搜索和重复入队的方法;原文 ACM 输入是整数,本文采用字符网格接口。
- 国内:labuladong:DFS 算法秒杀岛屿系列题目:参考网格转图、四方向及原地淹没的解释,说明正文中 visited 与修改原网格的取舍。
- 国内:OI Wiki:BFS(图论):依据队列遍历、访问标记与连通块搜索,解释正文中每座岛只计一次及总时间 O(mn) 的原因。
- 国外:LeetCode:200. Number of Islands:依据原题字符陆地、水与水平垂直连接的定义,确定正文中矩形输入、四邻接及边缘岛屿的处理。
- 国外:Python 官方文档:collections.deque:两端加入、弹出的复杂度及
maxlen的行为,解释 BFS 为什么使用无容量上限的deque,以及popleft()与list.pop(0)的区别。
相关面经
11 篇小红书
国际化开发工程师算法:岛屿数量(Hot 100,mid)
科大讯飞、首都在线、宇量晟、四川长虹、度小满、字节跳动、恒生电子、腾娱互动、金蝶、汽车之家
面试官人很好,不刁难,卡顿时也会及时引导,上来先拷打项目,每次面字节都会给我一种面试官想深究的感觉,他会一直朝一个方向问,直到你答不上来。之后就是八股,上来问agent相关rag,上下文工程,这块八股我非常薄弱,周旋了几轮后,面试官看出来我agent这块一般开始问服务端(此时我就能感觉出来我挂了),后面问服务端的八股,sse和websocket,线程池,缓存,mysql的一些基础八股(已经能够感觉到水时间了)。算法题:岛屿数量。dfs秒了。最后反问:全栈来说,agent所有东西也要吃透掌握吗。面试官说ai时代这是一个开发er具备的基本素质了,企业都希望招一个前端后端agent都能干的开发。
字节跳动
后端开发工程师岛屿数量算法题。
阿里巴巴
AI 应用研发工程师手撕二:岛屿数量,先DFS再BFS
百度
算法题:岛屿数量,先说思路,然后编写核心代码





