图像渲染 图解题解
这道题到底在问什么
- 输入
- image=[[1,1,1],[1,1,0],[1,0,1]], sr=1, sc=1, color=2
- 输出
- [[2,2,2],[2,2,0],[2,0,1]]
先想最直接的笨办法
从栈顶弹出 (1, 1)(紫色),挨个查它的上下左右四个邻居,把还是原色 1 的染掉。(动画第 7 步)
最优解:一步一步想明白
- 3记住「记原色 → 栈里弹一个、染四周同色的、再入栈」,下面逐帧套它。
- 4先看起点 (1, 1)(紫色),它的原色 old = 1。我们要把和它连通的同色格子都染成 2。
- 5判一下:新色 2 和原色 1 不一样,不会出现「越染越像原色」的死循环,可以放心开染。要是两者相等,这一步就该直接返回了。
- 6先把起点 (1, 1) 染成 2(变绿),压进栈。接下来不断从栈里弹格子、向四周扩散。
- 7从栈顶弹出 (1, 1)(紫色),挨个查它的上下左右四个邻居,把还是原色 1 的染掉。
- 8往上看邻居 (0, 1):它还是原色 1,和起点同色又挨着,符合条件,要把它也染上。
- 9把 (0, 1) 染成 2(变绿)、压进栈,等会儿再从它向外扩。
- 10往左看邻居 (1, 0):它还是原色 1,和起点同色又挨着,符合条件,要把它也染上。
- 11把 (1, 0) 染成 2(变绿)、压进栈,等会儿再从它向外扩。
- 12从栈顶弹出 (1, 0)(紫色),挨个查它的上下左右四个邻居,把还是原色 1 的染掉。
- 13往下看邻居 (2, 0):它还是原色 1,和起点同色又挨着,符合条件,要把它也染上。
- 14把 (2, 0) 染成 2(变绿)、压进栈,等会儿再从它向外扩。
- 15往上看邻居 (0, 0):它还是原色 1,和起点同色又挨着,符合条件,要把它也染上。
- 16把 (0, 0) 染成 2(变绿)、压进栈,等会儿再从它向外扩。
- 17从栈顶弹出 (0, 0)(紫色),挨个查它的上下左右四个邻居,把还是原色 1 的染掉。
- 18从栈顶弹出 (2, 0)(紫色),挨个查它的上下左右四个邻居,把还是原色 1 的染掉。
- 19从栈顶弹出 (0, 1)(紫色),挨个查它的上下左右四个邻居,把还是原色 1 的染掉。
- 20往右看邻居 (0, 2):它还是原色 1,和起点同色又挨着,符合条件,要把它也染上。
- 21把 (0, 2) 染成 2(变绿)、压进栈,等会儿再从它向外扩。
- 22从栈顶弹出 (0, 2)(紫色),挨个查它的上下左右四个邻居,把还是原色 1 的染掉。
- 23栈空了,扩散结束。这一片四连通的 1 全被染成了 2(绿色),一共 6 个格子;右下角那个 1 被 0 隔开、够不着,保持不变。最终网格就是答案。
⚠️ 容易写错的地方
✗ 错:新色等于原色还照常填
✓ 对:开头判 old == color 就直接返回
相等时染过的格子仍被看成「同原色」,会反复入栈、陷入死循环
✗ 错:把八方向都算连通
✓ 对:只看上、下、左、右四个方向
本题是四连通,对角线不算相邻,右下角的孤立同色格不该被染
✗ 错:判断时拿 color 比而不是 old 比
✓ 对:邻居要和起点原色 old 比较
一旦染过的格变成 color,再用 color 去比就会把刚染的又当成目标,逻辑乱套
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def floodFill(self, image: List[List[int]], sr: int, sc: int, color: int) -> List[List[int]]:
old = image[sr][sc]
if old == color:
return image
m, n = len(image), len(image[0])
stack = [(sr, sc)]
image[sr][sc] = color
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 image[nr][nc] == old:
image[nr][nc] = color
stack.append((nr, nc))
return imageC++
#include <vector>
using namespace std;
class Solution {
public:
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {
int old = image[sr][sc];
if (old == color) return image;
int m = image.size(), n = image[0].size();
vector<pair<int,int>> st{{sr, sc}};
image[sr][sc] = color;
int dirs[5] = {1,0,-1,0,1};
while (!st.empty()) {
auto [r, c] = st.back(); st.pop_back();
for (int d = 0; d < 4; ++d) {
int nr = r + dirs[d], nc = c + dirs[d+1];
if (nr >= 0 && nr < m && nc >= 0 && nc < n && image[nr][nc] == old) {
image[nr][nc] = color;
st.push_back({nr, nc});
}
}
}
return image;
}
};Java
import java.util.*;
class Solution {
public int[][] floodFill(int[][] image, int sr, int sc, int color) {
int old = image[sr][sc];
if (old == color) return image;
int m = image.length, n = image[0].length;
Deque<int[]> st = new ArrayDeque<>();
st.push(new int[]{sr, sc});
image[sr][sc] = color;
int[] dirs = {1,0,-1,0,1};
while (!st.isEmpty()) {
int[] cur = st.pop();
for (int d = 0; d < 4; d++) {
int nr = cur[0] + dirs[d], nc = cur[1] + dirs[d + 1];
if (nr >= 0 && nr < m && nc >= 0 && nc < n && image[nr][nc] == old) {
image[nr][nc] = color;
st.push(new int[]{nr, nc});
}
}
}
return image;
}
}复杂度
时间
O(m·n)
每个格子最多被染一次、入栈出栈各一次,m、n 是网格的行数和列数
空间
O(m·n)
最坏情况(整张图同色)栈里会装下几乎所有格子;递归写法则是递归栈同量级
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 图像渲染 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
用 DFS 还是 BFS?有区别吗?+
都行,结果完全一样。flood fill 只要把整片连通同色区访问一遍即可,不要求最短路径,所以 DFS(栈或递归)和 BFS(队列)都能做,复杂度同为 O(m·n)。把栈换成队列就是 BFS。递归 DFS 最简洁,但图很大时可能爆系统栈,这时改迭代更稳。
可以不用额外的 visited 数组吗?+
可以。本题的巧妙处在于「染色」本身就是标记:一个格子被染成 color 后,颜色不再等于 old,自然就不会被再次选中。所以不需要单独的 visited,省了空间——前提正是开头那个 old != color 的保证。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 图像渲染 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。