[Leetcode 200] 岛屿数量

type
status
date
summary
tags
category
icon
password
featured
freq
difficulty
给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。可以假设该网格的四条边均被水包围。
notion image
 
示例 1:
示例 2:
提示:
  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值为 '0' 或 '1'
 

解法一:DFS

这题是经典的在网格里数连通块的问题,核心就是遇到一块陆地,就把整块岛都“淹掉”,计数+1
可以把整个 grid 看成一张图:
  • '1' 是陆地节点
  • '0' 是水,不连通
我们从左上到右下扫一遍网格:
  • 一旦看到一个还没被处理过的 '1',就说明发现了一座新岛,答案先 +1。
  • 然后从这个格子出发,往四个方向(上、下、左、右)递归 dfs 把能连到的陆地都“染掉”
  • 染色的方式很简单粗暴:直接把 '1' 改成 '0',表示“已经访问过 / 已经淹掉”。
这样,当我们继续往后遍历时,就不会重复数同一座岛了。
整个过程就是:发现岛屿入口 → 一路灌水把整块岛全变成 0
用递归 DFS 写起来非常直观,但如果地图非常大、某一块岛非常连成一大片,递归深度可能比较深(Python 可能有栈深限制)。
复杂度
  • 时间:O(m·n),每个格子最多被访问一次。
  • 空间:最坏 O(m·n)(递归栈深度)。

解法二:BFS (推荐)

BFS 的思路和 DFS 一样,都是发现一块岛就整块灌水。
它们只是遍历一块岛的方法不同,区别在:
DFS = 一条路一直往下
BFS = 从起点一圈一圈向外扩散
具体做法:
  • 扫描网格,遇到 '1' 就说明发现新岛,结果 +1。
  • 把这个格子加进队列,然后开始做 BFS:
    • 从队列中弹出一个格子,把它视为当前“水淹到的地方”;
    • 看它四个方向有没有 '1',有的话就入队,并立刻改成 '0' 标记成“访问过”。
  • 队列空了,说明这块岛全部处理完了,然后继续扫网格,找下一块岛。
和 DFS 一样,本质是遍历整块连通区域。BFS用队列代替递归栈,更不容易出现栈溢出。
 
复杂度
  • 时间:O(m·n),每个格子也最多入队 / 出队一次。
  • 空间:O(m·n),最坏情况下队列里会装下整块大岛的一大部分。

解法三:并查集(Union-Find,“把陆地分组”)

并查集的思路是“把所有相邻的陆地归成一个集合”:
  1. 把每个陆地格子 (i, j) 看成一个节点,给它一个编号,比如 id = i * cols + j
  1. 遍历网格,当我们看到一个 '1' 时,就看它上面和左边(已经处理过的方向)是不是 '1'
      • 是的话,就把它们做一次 union,表示“这两个格子属于同一座岛”。
      • 之所以只看上和左,是因为右和下会在将来的遍历里遇到,不需要重复连。
  1. 所有 union 完毕后,我们再扫一遍所有 '1',把它们的“根节点”找出来,根的不同个数就是岛的个数
这种做法像是“分组统计”:每组是一座岛。
复杂度
  • 时间:O(m·n) 遍历一次网格 + 若干次 union / find
    • 平均近似 O(m·n · α(mn)),其中 α 是反 Ackermann 函数,极慢,可以认为接近常数,所以几乎是O(m·n)。
  • 空间:O(m·n),需要为每个陆地格子维护一个并查集节点(parent 映射)。
上一篇
[Leetcode 179] 最大数
下一篇
Demystifying Agentic Search Engines