哈喽,大家好呀,我是蔚蓝,今天我们来聊聊算法面试里的「岛屿问题」。
你有没有遇到过这种情况:刷 LeetCode 的时候,看到岛屿数量、最大面积、孤岛总面积这些题,感觉都差不多,但每次写都要重新想一遍逻辑,代码写得磕磕绊绊,面试的时候更是容易慌。
我之前也是这样。直到我发现,岛屿问题其实就是一套固定的「三步走」模板,掌握了这个模板,8 道经典变体都能秒杀。
岛屿问题是什么
给你一个二维网格,1 代表陆地,0 代表水。你要做的事情可能是:
- 数有多少个岛屿
- 找最大的岛屿面积
- 计算岛屿周长
- 沉没孤岛(不靠边界的岛)
这些题目看起来五花八门,但本质都是在网格上做 DFS/BFS 遍历。
为什么?

因为岛屿问题的核心就三个字:遍历、发现、扩散。
你遍历整个网格,发现一块陆地,然后从这块陆地出发,把所有相连的陆地都标记为「已访问」,这样就完成了一个岛屿的处理。
三步走模板
我把岛屿问题的解法总结成了一个「三步走」模板,记住这个模板,所有岛屿题都能套。
第一步:定义全局变量
1 2 3 4
| static int row, col; static int[][] grid; static boolean[][] visited; static int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
|
这四个变量是岛屿问题的「标配」,每道题都要用到。
dirs 数组存的是上下左右四个方向的偏移量,后面 DFS 的时候会用到。
第二步:定义 DFS 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| static void dfs(int x, int y) { if (x < 0 || x >= row || y < 0 || y >= col) return; if (grid[x][y] == 0 || visited[x][y]) return;
visited[x][y] = true;
for (int[] dir : dirs) { dfs(x + dir[0], y + dir[1]); } }
|
DFS 函数的逻辑很清晰:
- 先判断终止条件(越界、是水、已访问)
- 标记当前位置为已访问
- 根据题目要求做具体操作(染色、统计面积、统计周长等)
- 向四个方向递归
这个模板是固定的,不同题目只需要调整第 3 步的逻辑。
第三步:遍历网格,进入 DFS

1 2 3 4 5 6 7 8
| for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (grid[i][j] == 1 && !visited[i][j]) { dfs(i, j); } } }
|
双重循环遍历整个网格,遇到陆地且未访问过,就进入 DFS。
有些题目需要在这里更新答案,比如岛屿数量就是每次进入 DFS 前 ans++。
💡 记忆口诀:全局变量开好头,终止条件先判断,标记扩散两手抓,双重循环遍网格
8 道经典变体
掌握了三步走模板,我们来看看 8 道经典变体怎么套用。
1. 岛屿数量
题目:给定一个由 1(陆地)和 0(水)组成的矩阵,计算岛屿的数量。
思路:遍历网格,遇到陆地时岛屿数量 +1,然后 DFS 标记所有相连的陆地。
1 2 3 4 5 6 7 8 9
| int ans = 0; for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (!visited[i][j] && grid[i][j] == 1) { ans++; dfs(i, j); } } }
|
注意:不要在调用 dfs 之前设置 visited[i][j] = true,这会导致 DFS 内部的检查直接返回。
2. 岛屿的最大面积
题目:计算岛屿的最大面积。
思路:在 DFS 时记录每个岛屿的面积,维护最大值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| static int count;
static void dfs(int x, int y) { if (x < 0 || x >= row || y < 0 || y >= col) return; if (visited[x][y] || grid[x][y] != 1) return;
visited[x][y] = true; count++;
for (int[] dir : dirs) { dfs(x + dir[0], y + dir[1]); } }
int maxArea = 0; for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (!visited[i][j] && grid[i][j] == 1) { count = 0; dfs(i, j); maxArea = Math.max(maxArea, count); } } }
|
3. 孤岛的总面积
题目:孤岛是那些完全不与矩阵边界接触的岛屿,计算所有孤岛的总面积。
思路:先从四条边界开始 DFS,将与边界相连的岛屿标记为「非孤岛」,然后统计剩余岛屿的面积。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| for (int i = 0; i < row; i++) { if (grid[i][0] == 1) dfs(i, 0); if (grid[i][col-1] == 1) dfs(i, col-1); } for (int j = 0; j < col; j++) { if (grid[0][j] == 1) dfs(0, j); if (grid[row-1][j] == 1) dfs(row-1, j); }
int totalArea = 0; for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (!visited[i][j] && grid[i][j] == 1) { count = 0; dfs(i, j); totalArea += count; } } }
|
这道题的关键是「逆向思维」:先排除掉靠边界的岛屿,剩下的就是孤岛。
4. 沉没孤岛
题目:将所有孤岛(不与边界相连的岛屿)沉没,即将孤岛的 1 变为 0。
思路:
- 从四条边界开始 DFS,将与边界相连的岛屿标记为特殊值(如
2)
- 遍历整个网格:将
1 变为 0(沉没孤岛),将 2 变回 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| for (int i = 0; i < row; i++) { if (grid[i][0] == 1) dfs(grid, i, 0); if (grid[i][col-1] == 1) dfs(grid, i, col-1); } for (int j = 0; j < col; j++) { if (grid[0][j] == 1) dfs(grid, 0, j); if (grid[row-1][j] == 1) dfs(grid, row-1, j); }
for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (grid[i][j] == 1) grid[i][j] = 0; else if (grid[i][j] == 2) grid[i][j] = 1; } }
|
这道题和孤岛面积的思路类似,都是先处理边界,再处理内部。
5. 水流问题
题目:给定一个矩阵表示地形高度,水只能从高处流向低处或等高处。找出所有既能流向第一组边界(上边界和左边界)又能流向第二组边界(下边界和右边界)的单元格。
思路:逆向思维,从边界出发,逆向寻找能流向该边界的单元格。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| boolean[][] first = new boolean[row][col]; boolean[][] second = new boolean[row][col];
for (int i = 0; i < row; i++) { dfs(grid, first, i, 0); } for (int j = 0; j < col; j++) { dfs(grid, first, 0, j); }
for (int i = 0; i < row; i++) { dfs(grid, second, i, col-1); } for (int j = 0; j < col; j++) { dfs(grid, second, row-1, j); }
int ans = 0; for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (first[i][j] && second[i][j]) { ans++; } } }
|
注意 DFS 的逻辑要改成「逆向」:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| static void dfs(int[][] grid, boolean[][] visited, int x, int y) { if (visited[x][y]) return; visited[x][y] = true;
for (int i = 0; i < 4; i++) { int nx = x + dirs[i][0]; int ny = y + dirs[i][1];
if (nx >= 0 && nx < row && ny >= 0 && ny < col) { if (grid[nx][ny] >= grid[x][y]) { dfs(grid, visited, nx, ny); } } } }
|
这道题的关键是「逆向思维」:不是从高处往低处流,而是从边界往高处流。
6. 建造最大岛屿
题目:给定一个由 1(陆地)和 0(水)组成的矩阵,你可以将一个水单元格变为陆地,求能形成的最大岛屿面积。
思路:
- 第一次遍历:用不同编号(从 2 开始)标记每个岛屿,用 HashMap 记录每个编号对应的面积
- 第二次遍历:对每个水单元格,计算将其变为陆地后能连接的岛屿总面积
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| Map<Integer, Integer> islandArea = new HashMap<>(); int mark = 2; int maxArea = 0;
for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (grid[i][j] == 1) { count = 0; dfs(grid, i, j, mark); islandArea.put(mark, count); maxArea = Math.max(maxArea, count); mark++; } } }
for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (grid[i][j] == 0) { Set<Integer> neighborIslands = new HashSet<>();
for (int k = 0; k < 4; k++) { int ni = i + dirs[k][0]; int nj = j + dirs[k][1];
if (ni >= 0 && ni < row && nj >= 0 && nj < col && grid[ni][nj] > 1) { neighborIslands.add(grid[ni][nj]); } }
int totalArea = 1; for (int islandMark : neighborIslands) { totalArea += islandArea.get(islandMark); } maxArea = Math.max(maxArea, totalArea); } } }
|
注意:
- 使用
Set 避免重复计算相邻的同一岛屿
- 如果网格全是陆地(没有 0),答案就是最大原始岛屿面积
7. 岛屿的周长
题目:计算岛屿的周长。
思路一:遍历统计
- 每个陆地单元格贡献 4 条边
- 每对相邻的陆地单元格,减少 2 条边(因为共享边界)
- 公式:
周长 = 陆地数 × 4 - 相邻对数 × 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int landCount = 0; int coverCount = 0;
for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (grid[i][j] == 1) { landCount++; if (i + 1 < row && grid[i + 1][j] == 1) coverCount++; if (j + 1 < col && grid[i][j + 1] == 1) coverCount++; } } }
System.out.println(landCount * 4 - coverCount * 2);
|
思路二:DFS 统计边界
在 DFS 过程中,遇到边界或水时,周长 +1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| static int perimeter;
static void dfs(int[][] grid, int x, int y) { if (x < 0 || x >= row || y < 0 || y >= col) { perimeter++; return; } if (grid[x][y] == 0) { perimeter++; return; } if (grid[x][y] != 1) return;
grid[x][y] = 2;
for (int[] dir : dirs) { dfs(grid, x + dir[0], y + dir[1]); } }
|
总结

岛屿问题的 8 道经典变体,核心都是「三步走」模板:
| 问题 |
核心思路 |
| 岛屿数量 |
遍历 + DFS 标记 |
| 最大面积 |
DFS 时记录面积 |
| 孤岛面积 |
先标记边界岛屿,再统计 |
| 沉没孤岛 |
边界岛屿标记为 2,1→0,2→1 |
| 水流问题 |
逆向 DFS,求交集 |
| 建造最大岛屿 |
编号标记 + HashMap + 遍历水格 |
| 岛屿周长 |
陆地×4 - 相邻×2 或 DFS 统计边界 |
通用技巧:
- 方向数组:
int[][] dirs = {{0,1}, {1,0}, {-1,0}, {0,-1}}
- 边界检查:
x < 0 || x >= row || y < 0 || y >= col
- 标记已访问:
visited[x][y] = true 或直接修改 grid[x][y] 的值
记住这个模板,面试的时候遇到岛屿问题,直接套就行了。
碎碎念
我最近在准备考研机试,刷了很多岛屿问题的题。一开始也是每道题都要重新想一遍,写得很慢。
后来我发现,这些题目的代码结构几乎一模一样,只是 DFS 里的逻辑稍微有点不同。于是我把这个「三步走」模板总结出来,现在遇到岛屿问题,基本上 5 分钟就能写完。
算法题不是靠死记硬背,而是要找到规律,提炼出模板。掌握了模板,你就能举一反三,遇到新题也不慌。
岛屿问题就是这样,掌握了三步走,8 道题都能秒。
希望这篇文章对你有帮助。如果你也在准备算法面试,可以把这个模板背下来,面试的时候直接套。
工具在变,模板不变。