当前:LC733 · 图像渲染 · 首次出现于 Day 29 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC733 · Flood Fill · 网格 / DFS

图像渲染:同色连通块整体换色

从起点出发,只走颜色等于原色的格子,改成新色后再向上下左右扩散。颜色不同、越界,都停。

记下起点原色 orig。若 orig == color 直接返回,否则会死循环。DFS 从起点向四方向走:越界或当前格不是 orig 就返回;否则改成 color,再递归四邻。改色本身就是访问标记。时间 O(m·n),空间 O(m·n)。

时间 O(m·n)空间 O(m·n)结论先行 · 全文约 6 节
导读

给你一张二维整数图像,一个起点 (sr, sc),一个新颜色 color。从起点出发,把所有与起点四向连通、且颜色和起点原来一样的格子,全部改成 color,返回改完的图像。对角不相通。

主例 image = [[1,1,1],[1,1,0],[1,0,1]],从 (1,1) 开始,新色是 2。起点原色是 1。和它四向连着的 1 共六格,改完是 [[2,2,2],[2,2,0],[2,0,1]]。右下角 (2,2) 也是 1,但上下左右都是 0 或界外,和起点不相通,保持 1。

本文要回答:扩散的停条件为什么是「颜色不等于原色」,以及为什么 orig 等于 color 时必须立刻返回。

扩散只认原色

油漆桶工具做的就是这件事:点一下,整块同色区域换色。网格上「整块」的精确定义是四向连通:上下左右能走到、中间不改色。对角不算。缺的不是「改颜色」这个动作,而是一条停下来的规则:什么格子不该被改。

先把起点当下的颜色记成 orig。之后每一次递归只问三件事:有没有越界;这一格现在是不是还等于 orig;是的话改成 color,再向四个方向走。改过的格子颜色已经不是 orig,下一次再踩到它会立刻返回。所以不必另开 visited 数组,改色本身就是标记。

若新色和原色相同,改色不会改变「等于 orig」这个判断,递归会在同一块区域里永远转。必须在进 DFS 之前比较 orig 和 color,相等就原图返回。这不是优化,是正确性。

用手走主例。起点 (1,1),orig=1,color=2。先改 (1,1)。向上到 (0,1) 仍是 1,改;再向左到 (0,0)、向右到 (0,2),都是 1,改。从 (1,1) 向左到 (1,0) 是 1,改;再向下到 (2,0) 是 1,改。(1,1) 向右是 (1,2)=0,向南是 (2,1)=0,都不是 orig,停。演示场景从起点扩到 (0,0),再收成整块变 2,对应这六格。

(2,2) 是 1,但它的四邻是 (1,2)=0、(2,1)=0,以及两处界外。没有任何一条只走 1 的路能从 (1,1) 走到它。洪水填不到这里,它保持 1。若误用八向连通,它会经对角线被改掉,答案就错了。

整张图都是同一颜色时,一次填充会改完全图。起点已经是目标色,函数什么都不做。空的邻接、越界,都落在 DFS 开头那条守卫上。每个格子进出常数次,时间按格子数算。

颜色即标记格子被改成新色之后,不再等于 orig,不会被第二次推进队列或递归。visited 和改色是同一件事。正因为如此,orig == color 时这条标记失效,必须短路。
从 (1,1) 填充 color=2
1
1
1
1
1
0
1
0
1
原色 1新色 2
orig=1,起点 (1,1)

Go:DFS 填充

solution.goGo
func floodFill(image [][]int, sr, sc, color int) [][]int {
orig := image[sr][sc]
if orig == color { return image }
m, n := len(image), len(image[0])
var dfs func(int, int)
dfs = func(i, j int) {
if i < 0 || i >= m || j < 0 || j >= n || image[i][j] != orig { return }
image[i][j] = color
dfs(i+1, j); dfs(i-1, j); dfs(i, j+1); dfs(i, j-1)
}
dfs(sr, sc)
return image
}

1先记下 orig。它和 color 相同就立刻返回,否则递归停不下来。

2守卫写在改色前面:越界或颜色已经不是 orig,连读四周都不必。

3image[i][j] = color 既是涂色,也是「已访问」。主例六格各被涂一次。

4四个方向各进一次。0 和右下角那个 1 都会在守卫处被挡回来。

总结

从起点四向扩散,只改原色格子。主例六格变 2,右下角孤立的 1 不动。

  • 停条件是「不是 orig」,不是「已经是新色」——两者在 orig==color 时不是一回事。
  • 改色兼任访问标记,不必另开数组。
  • 四向连通,对角不算。主例 (2,2) 因此保住原色。
同族题目
LC200岛屿数量LC695岛屿的最大面积LC130被围绕的区域