飞地的数量 图解题解
这道题到底在问什么
- 输入
- grid 见右(5×5),靠边的陆地能走出去,中间一团走不出去
- 输出
- 3(中间那块 3 格陆地是飞地)
最优解:一步一步想明白
- 3记住「从边界陆地往里 DFS 标记可达 → 剩下没标记的陆地就是飞地」,下面逐帧套它。
- 4第一步,沿着最外面一圈走一遍。每碰到一个边界上的陆地,就从它钻进去,把所有能连通的陆地都标成「能走到边界」。
- 5边界格 (0, 0) 是陆地(紫),说明这块陆地能走到边界。从它钻进去,把连着的陆地全标蓝(能出去)。
- 6从 (0, 0) 看四周:往下、右也是陆地,把它们一并标成「能出去」(蓝)、继续往里走。
- 7边界格 (0, 1) 也是陆地,不过它刚才已经被同一片连通区灌到、标蓝了,不必再灌,看下一个。
- 8检查边界格 (0, 2):是海不是陆地,没什么可灌的,继续看下一个边界格。
- 9检查边界格 (0, 3):是海不是陆地,没什么可灌的,继续看下一个边界格。
- 10边界格 (0, 4) 是陆地(紫),说明这块陆地能走到边界。从它钻进去,把连着的陆地全标蓝(能出去)。
- 11从 (0, 4) 看四周:往下也是陆地,把它们一并标成「能出去」(蓝)、继续往里走。
- 12检查边界格 (4, 0):是海不是陆地,没什么可灌的,继续看下一个边界格。
- 13检查边界格 (4, 1):是海不是陆地,没什么可灌的,继续看下一个边界格。
- 14检查边界格 (4, 2):是海不是陆地,没什么可灌的,继续看下一个边界格。
- 15检查边界格 (4, 3):是海不是陆地,没什么可灌的,继续看下一个边界格。
- 16检查边界格 (4, 4):是海不是陆地,没什么可灌的,继续看下一个边界格。
- 17边界格 (1, 0) 也是陆地,不过它刚才已经被同一片连通区灌到、标蓝了,不必再灌,看下一个。
- 18检查边界格 (2, 0):是海不是陆地,没什么可灌的,继续看下一个边界格。
- 19检查边界格 (3, 0):是海不是陆地,没什么可灌的,继续看下一个边界格。
- 20边界格 (1, 4) 也是陆地,不过它刚才已经被同一片连通区灌到、标蓝了,不必再灌,看下一个。
- 21检查边界格 (2, 4):是海不是陆地,没什么可灌的,继续看下一个边界格。
- 22检查边界格 (3, 4):是海不是陆地,没什么可灌的,继续看下一个边界格。
- 23四条边走完。左上角那块(含 (0,0)、(0,1)、(1,0))和右上角那块(含 (0,4)、(1,4))都从边界灌到了,全标蓝。海格(灰点)挡住了继续扩散,所以只有这两块靠边的连通陆地被灌到、标蓝;中间那团没被灌到。
- 24倒灌结束。接下来把整张网格再扫一遍:凡是陆地、却没被标蓝的,就是怎么走都出不去的飞地,标绿、计数。
- 25(2, 2) 是陆地,可它没被标蓝,说明从边界灌不到它、它也走不到边界,是飞地(绿)。飞地计数累加到 1。
- 26(2, 3) 是陆地,可它没被标蓝,说明从边界灌不到它、它也走不到边界,是飞地(绿)。飞地计数累加到 2。
- 27(3, 2) 是陆地,可它没被标蓝,说明从边界灌不到它、它也走不到边界,是飞地(绿)。飞地计数累加到 3。
- 28整张网格扫完,绿色的飞地一共 3 格,这就是答案。回头看:从边界倒灌标记一遍 O(mn)、再扫一遍计数 O(mn),整体就是 O(mn)。
⚠️ 容易写错的地方
✗ 错:正着判断每块陆地能否到边界
✓ 对:反过来从边界倒灌、标记可达
正着对每块陆地都搜一次会重复劳动;从边界灌一次就把所有「能出去」的一次性标完,剩下的才是飞地
✗ 错:只灌四个角或只灌第一行
✓ 对:上下左右四条边的每个格都要查
陆地可能从任意一条边的中间通向外面,漏掉某条边会把本该出去的陆地误判成飞地
✗ 错:另开 visited 还忘了同步
✓ 对:直接把访问过的陆地置 0(或单独 visited 但别漏标)
不做标记,DFS 会在同一片陆地里来回打转、甚至死循环
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def numEnclaves(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
def flood(sr: int, sc: int) -> None:
# 显式栈迭代 DFS,避免大网格递归爆栈
stack = [(sr, sc)]
grid[sr][sc] = 0
while stack:
r, c = stack.pop()
for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
nr, nc = r + dr, c + dc
if 0 <= nr < m and 0 <= nc < n and grid[nr][nc] == 1:
grid[nr][nc] = 0
stack.append((nr, nc))
# 从四条边上的每个陆地倒灌
for r in range(m):
for c in (0, n - 1):
if grid[r][c] == 1:
flood(r, c)
for c in range(n):
for r in (0, m - 1):
if grid[r][c] == 1:
flood(r, c)
return sum(sum(row) for row in grid)C++
#include <vector>
using namespace std;
class Solution {
public:
int numEnclaves(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
int dirs[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
// 显式栈迭代 DFS,避免大网格递归爆栈
auto flood = [&](int sr, int sc) {
vector<pair<int,int>> stk{{sr, sc}};
grid[sr][sc] = 0;
while (!stk.empty()) {
auto [r, c] = stk.back(); stk.pop_back();
for (auto &d : dirs) {
int nr = r + d[0], nc = c + d[1];
if (nr >= 0 && nr < m && nc >= 0 && nc < n && grid[nr][nc] == 1) {
grid[nr][nc] = 0; stk.push_back({nr, nc});
}
}
}
};
for (int r = 0; r < m; ++r) for (int c : {0, n - 1})
if (grid[r][c] == 1) flood(r, c);
for (int c = 0; c < n; ++c) for (int r : {0, m - 1})
if (grid[r][c] == 1) flood(r, c);
int ans = 0;
for (auto &row : grid) for (int x : row) ans += x;
return ans;
}
};Java
import java.util.*;
class Solution {
public int numEnclaves(int[][] grid) {
int m = grid.length, n = grid[0].length;
int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
for (int r = 0; r < m; r++) for (int c : new int[]{0, n - 1})
if (grid[r][c] == 1) flood(grid, r, c, m, n, dirs);
for (int c = 0; c < n; c++) for (int r : new int[]{0, m - 1})
if (grid[r][c] == 1) flood(grid, r, c, m, n, dirs);
int ans = 0;
for (int[] row : grid) for (int x : row) ans += x;
return ans;
}
// 显式栈迭代 DFS,避免大网格递归爆栈
private void flood(int[][] grid, int sr, int sc, int m, int n, int[][] dirs) {
Deque<int[]> stk = new ArrayDeque<>();
stk.push(new int[]{sr, sc});
grid[sr][sc] = 0;
while (!stk.isEmpty()) {
int[] cur = stk.pop();
for (int[] d : dirs) {
int nr = cur[0] + d[0], nc = cur[1] + d[1];
if (nr >= 0 && nr < m && nc >= 0 && nc < n && grid[nr][nc] == 1) {
grid[nr][nc] = 0; stk.push(new int[]{nr, nc});
}
}
}
}
}复杂度
时间
O(m·n)
边界 DFS 每个陆地格最多被访问一次,最后求和再扫一遍整张网格,都是格子总数级
空间
O(m·n)
迭代用的显式栈最坏(整张几乎全是连通陆地)能压到格子总数级;没有额外开 visited 数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 飞地的数量 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题和「被围绕的区域」(把被围的 O 变 X)是不是一类?+
是同一套「从边界倒灌」的思路。被围绕的区域也是先从边界出发,把所有连到边界的 O 标记成「不被围」,剩下的 O 才是真正被围、需要翻转的。两题都是「正着判断每个点麻烦,就反过来从边界标记可达」。
能不能用 BFS 或并查集做?+
都行。BFS 把所有边界陆地一起入队做多源扩散、标记可达,效果和 DFS 一样;并查集可以把所有陆地按连通块合并、再看每个块里有没有边界格,没有的块大小累加。三种写法时间都是 O(m·n) 级,DFS 最直接。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 飞地的数量 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。