1. 算法竞赛中的Flood Fill问题解析
Flood Fill(区域填充)是图形处理和算法竞赛中的经典问题,它要求从一个起始点出发,标记或处理所有连通的区域。这个问题在图像处理、迷宫求解、连通域分析等场景中有着广泛应用。
我第一次接触这个问题是在一场编程比赛中,题目要求计算岛屿数量。当时使用暴力解法导致超时,后来发现DFS(深度优先搜索)才是最高效的解决方案。这种"恍然大悟"的经历,相信很多算法竞赛选手都深有体会。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. DFS算法核心原理
2.1 深度优先搜索的工作机制
DFS采用"一条路走到黑"的策略,从起点开始尽可能深地探索每一个分支。当遇到死胡同时,算法会回溯到上一个分叉点继续探索。这种特性使其特别适合解决连通性问题。
python复制def dfs(x, y):
# 越界检查
if not (0 <= x < rows and 0 <= y < cols):
return
# 访问标记或条件检查
if visited[x][y] or grid[x][y] != target:
return
# 处理当前节点
visited[x][y] = True
# 向四个方向递归搜索
dfs(x+1, y)
dfs(x-1, y)
dfs(x, y+1)
dfs(x, y-1)
2.2 DFS在Flood Fill中的优势
相比BFS(广度优先搜索),DFS在解决Flood Fill问题时具有以下优势:
- 代码实现更简洁
- 不需要额外队列空间
- 递归形式更符合问题本质
- 在特定情况下效率更高
注意:当填充区域过大时,递归DFS可能导致栈溢出。这时可以改用栈实现的迭代DFS或BFS。
3. 典型应用场景与实现
3.1 岛屿数量问题
这是LeetCode上的经典题目(200. Number of Islands)。给定一个由'1'(陆地)和'0'(水)组成的二维网格,计算岛屿的数量。
python复制def numIslands(grid):
if not grid:
return 0
count = 0
rows, cols = len(grid), len(grid[0])
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
dfs(grid, i, j)
count += 1
return count
def dfs(grid, i, j):
if i<0 or j<0 or i>=len(grid) or j>=len(grid[0]) or grid[i][j] != '1':
return
grid[i][j] = '0' # 标记为已访问
dfs(grid, i+1, j)
dfs(grid, i-1, j)
dfs(grid, i, j+1)
dfs(grid, i, j-1)
3.2 图像填充工具
类似Photoshop中的油漆桶工具,其核心算法就是Flood Fill。我们可以用DFS实现一个简单的颜色填充:
python复制def floodFill(image, sr, sc, newColor):
oldColor = image[sr][sc]
if oldColor == newColor:
return image
def dfs(r, c):
if image[r][c] == oldColor:
image[r][c] = newColor
if r >= 1: dfs(r-1, c)
if r+1 < len(image): dfs(r+1, c)
if c >= 1: dfs(r, c-1)
if c+1 < len(image[0]): dfs(r, c+1)
dfs(sr, sc)
return image
4. 性能优化与边界处理
4.1 避免重复计算的技巧
- 及时标记已访问节点(修改原数组或使用额外visited数组)
- 在递归前进行条件判断,而不是递归后再判断
- 根据问题特点调整搜索方向顺序
4.2 常见错误与调试方法
- 栈溢出:改用迭代DFS或BFS
- 边界条件遗漏:仔细检查数组越界情况
- 无限递归:确保递归终止条件完备
- 方向数组使用不当:统一使用dx, dy数组管理方向
python复制# 更安全的方向数组写法
directions = [(-1,0),(1,0),(0,-1),(0,1)]
def dfs(x, y):
# ...
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < rows and 0 <= ny < cols:
dfs(nx, ny)
5. 竞赛中的进阶应用
5.1 连通分量统计
许多图论问题可以转化为Flood Fill问题。例如统计无向图中连通分量的数量:
python复制def countComponents(n, edges):
graph = [[] for _ in range(n)]
for a, b in edges:
graph[a].append(b)
graph[b].append(a)
visited = [False] * n
count = 0
def dfs(node):
visited[node] = True
for neighbor in graph[node]:
if not visited[neighbor]:
dfs(neighbor)
for i in range(n):
if not visited[i]:
dfs(i)
count += 1
return count
5.2 带有附加条件的Flood Fill
有时需要在填充时满足额外条件。例如"被围绕的区域"问题(LeetCode 130),要求只在边界相连的'O'不被填充:
python复制def solve(board):
if not board:
return
rows, cols = len(board), len(board[0])
def dfs(r, c):
if 0<=r<rows and 0<=c<cols and board[r][c] == 'O':
board[r][c] = 'T'
dfs(r+1, c)
dfs(r-1, c)
dfs(r, c+1)
dfs(r, c-1)
# 先从边界开始标记
for r in range(rows):
dfs(r, 0)
dfs(r, cols-1)
for c in range(cols):
dfs(0, c)
dfs(rows-1, c)
# 最后处理整个板
for r in range(rows):
for c in range(cols):
if board[r][c] == 'O':
board[r][c] = 'X'
elif board[r][c] == 'T':
board[r][c] = 'O'
6. 从DFS到其他解法
虽然DFS是解决Flood Fill问题的最直观方法,但在实际竞赛中,根据问题规模和数据特点,我们还需要掌握其他解法:
6.1 BFS实现
当处理大规模数据时,BFS的迭代特性可以避免递归深度过大:
python复制from collections import deque
def bfs_fill(grid, i, j):
queue = deque()
queue.append((i,j))
grid[i][j] = '0'
while queue:
x, y = queue.popleft()
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = x + dx, y + dy
if 0<=nx<len(grid) and 0<=ny<len(grid[0]) and grid[nx][ny] == '1':
grid[nx][ny] = '0'
queue.append((nx, ny))
6.2 并查集(Union-Find)解法
对于某些统计类问题,并查集可能是更高效的解决方案:
python复制class UnionFind:
def __init__(self, grid):
rows, cols = len(grid), len(grid[0])
self.count = 0
self.parent = [i for i in range(rows * cols)]
self.rank = [0] * (rows * cols)
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
self.count += 1
def find(self, i):
if self.parent[i] != i:
self.parent[i] = self.find(self.parent[i])
return self.parent[i]
def union(self, x, y):
rootx = self.find(x)
rooty = self.find(y)
if rootx != rooty:
if self.rank[rootx] > self.rank[rooty]:
self.parent[rooty] = rootx
else:
self.parent[rootx] = rooty
if self.rank[rootx] == self.rank[rooty]:
self.rank[rooty] += 1
self.count -= 1
def numIslands(grid):
if not grid:
return 0
rows = len(grid)
cols = len(grid[0])
uf = UnionFind(grid)
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
grid[i][j] = '0'
for x, y in [(-1,0),(1,0),(0,-1),(0,1)]:
ni, nj = i + x, j + y
if 0 <= ni < rows and 0 <= nj < cols and grid[ni][nj] == '1':
uf.union(i * cols + j, ni * cols + nj)
return uf.count
在实际比赛中,我通常会先写DFS版本,因为它实现快速。当遇到性能瓶颈时,再考虑优化为BFS或并查集。这种渐进式的解题策略在时间紧张的竞赛中非常实用。
