文章目录
题目
标题和出处
标题:图像渲染
出处:733. 图像渲染
难度
4 级
题目描述
要求
有一个图像以 m × n \texttt{m} \times \texttt{n} m×n 的整数网格 image \texttt{image} image 表示,其中 image[i][j] \texttt{image[i][j]} image[i][j] 表示该图像的像素值。
另外给定三个整数 sr \texttt{sr} sr、 sc \texttt{sc} sc 和 color \texttt{color} color。需要从像素 image[sr][sc] \texttt{image[sr][sc]} image[sr][sc] 开始对图像洪水填充。
为了完成洪水填充 ,考虑起始像素点以及与起始像素点在四个方向上连接且与起始像素点的颜色相同的像素点,以及与这些像素点在四个方向上连接且颜色相同的像色淀,以此类推。将上述所有像素点的颜色都改为 color \texttt{color} color。
返回经过洪水填充后的图像。
示例
示例 1:

输入: image = [[1,1,1],[1,1,0],[1,0,1]],sr = 1, sc = 1, color = 2 \texttt{image = [[1,1,1],[1,1,0],[1,0,1]],sr = 1, sc = 1, color = 2} 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]] \texttt{[[2,2,2],[2,2,0],[2,0,1]]} [[2,2,2],[2,2,0],[2,0,1]]
解析:从图像的正中间, (sr, sc) = (1, 1) \texttt{(sr, sc)} = \texttt{(1, 1)} (sr, sc)=(1, 1)(即红色的像素点)开始,在路径上所有符合条件的像素点的颜色都被更改成新颜色。
注意,右下角的像素点没有更改为 2 \texttt{2} 2,因为它不是在四个方向上与起始像素点相连的像素点。
示例 2:
输入: image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, color = 2 \texttt{image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, color = 2} image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, color = 2
输出: [[2,2,2],[2,2,2]] \texttt{[[2,2,2],[2,2,2]]} [[2,2,2],[2,2,2]]
数据范围
- m = image.length \texttt{m} = \texttt{image.length} m=image.length
- n = image[i].length \texttt{n} = \texttt{image[i].length} n=image[i].length
- 1 ≤ m, n ≤ 50 \texttt{1} \le \texttt{m, n} \le \texttt{50} 1≤m, n≤50
- 0 ≤ image[i][j], color < 2 16 \texttt{0} \le \texttt{image[i][j], color} < \texttt{2}^\texttt{16} 0≤image[i][j], color<216
- 0 ≤ sr < m \texttt{0} \le \texttt{sr} < \texttt{m} 0≤sr<m
- 0 ≤ sc < n \texttt{0} \le \texttt{sc} < \texttt{n} 0≤sc<n
解法一
思路和算法
首先获得 image [ sr ] [ sc ] \textit{image}[\textit{sr}][\textit{sc}] image[sr][sc] 的原始颜色 originalColor \textit{originalColor} originalColor,如果 originalColor = color \textit{originalColor} = \textit{color} originalColor=color,则原始颜色与新颜色相同,不需要改任何像素点的颜色,直接返回 image \textit{image} image。
如果 originalColor ≠ color \textit{originalColor} \ne \textit{color} originalColor=color,则需要从起始像素点 ( sr , sc ) (\textit{sr}, \textit{sc}) (sr,sc) 开始,将所有颜色相同且在四个方向上相连的像素点的颜色改为新颜色 color \textit{color} color。
由于需要改颜色的像素点在图中相连,因此可以使用广度优先搜索实现图像渲染。
首先将 image [ sr ] [ sc ] \textit{image}[\textit{sr}][\textit{sc}] image[sr][sc] 的颜色改为 color \textit{color} color,然后访问与起始像素点连接且颜色为 originalColor \textit{originalColor} originalColor 的所有像素点,将颜色改为 color \textit{color} color。遍历结束之后,返回 image \textit{image} image。
由于只有当 originalColor ≠ color \textit{originalColor} \ne \textit{color} originalColor=color 时才需要执行图像渲染,且每个需要改颜色的像素点在访问之后都改为新颜色,因此可以根据像素点的颜色判断像素点是否访问过,像素点的颜色为 originalColor \textit{originalColor} originalColor 表示未访问,像素点的颜色不为 originalColor \textit{originalColor} originalColor 表示已访问。
代码
java
class Solution {
static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public int[][] floodFill(int[][] image, int sr, int sc, int color) {
int originalColor = image[sr][sc];
if (originalColor == color) {
return image;
}
int m = image.length, n = image[0].length;
image[sr][sc] = color;
Queue<int[]> queue = new ArrayDeque<int[]>();
queue.offer(new int[]{sr, sc});
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int row = cell[0], col = cell[1];
for (int[] dir : dirs) {
int newRow = row + dir[0], newCol = col + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n && image[newRow][newCol] == originalColor) {
image[newRow][newCol] = color;
queue.offer(new int[]{newRow, newCol});
}
}
}
return image;
}
}
复杂度分析
-
时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是图像 image \textit{image} image 的行数和列数。广度优先搜索最多需要访问每个像素点一次。
-
空间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是图像 image \textit{image} image 的行数和列数。队列需要 O ( m n ) O(mn) O(mn) 的空间。
解法二
思路和算法
如果 image [ sr ] [ sc ] \textit{image}[\textit{sr}][\textit{sc}] image[sr][sc] 的原始颜色 originalColor \textit{originalColor} originalColor 与新颜色 color \textit{color} color 不同,也可以使用深度优先搜索实现图像渲染。
首先将 image [ sr ] [ sc ] \textit{image}[\textit{sr}][\textit{sc}] image[sr][sc] 的颜色改为 color \textit{color} color,然后访问与起始像素点连接且颜色为 originalColor \textit{originalColor} originalColor 的所有像素点,将颜色改为 color \textit{color} color。遍历结束之后,返回 image \textit{image} image。
由于只有当 originalColor ≠ color \textit{originalColor} \ne \textit{color} originalColor=color 时才需要执行图像渲染,且每个需要改颜色的像素点在访问之后都改为新颜色,因此可以根据像素点的颜色判断像素点是否访问过,像素点的颜色为 originalColor \textit{originalColor} originalColor 表示未访问,像素点的颜色不为 originalColor \textit{originalColor} originalColor 表示已访问。
代码
java
class Solution {
static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int[][] image;
int m, n;
int originalColor;
int color;
public int[][] floodFill(int[][] image, int sr, int sc, int color) {
originalColor = image[sr][sc];
if (originalColor == color) {
return image;
}
this.image = image;
this.m = image.length;
this.n = image[0].length;
this.color = color;
dfs(sr, sc);
return image;
}
public void dfs(int row, int col) {
image[row][col] = color;
for (int[] dir : dirs) {
int newRow = row + dir[0], newCol = col + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n && image[newRow][newCol] == originalColor) {
dfs(newRow, newCol);
}
}
}
}
复杂度分析
-
时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是图像 image \textit{image} image 的行数和列数。深度优先搜索最多需要访问每个像素点一次。
-
空间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是图像 image \textit{image} image 的行数和列数。递归调用栈需要 O ( m n ) O(mn) O(mn) 的空间。
解法三
预备知识
该解法涉及到并查集。
并查集是一种树型的数据结构,用于处理不相交集合的合并与查询问题。
思路和算法
首先获得 image [ sr ] [ sc ] \textit{image}[\textit{sr}][\textit{sc}] image[sr][sc] 的原始颜色 originalColor \textit{originalColor} originalColor,如果 originalColor = color \textit{originalColor} = \textit{color} originalColor=color,则原始颜色与新颜色相同,不需要改任何像素点的颜色,直接返回 image \textit{image} image。
如果 originalColor ≠ color \textit{originalColor} \ne \textit{color} originalColor=color,则需要执行图像渲染。由于需要改颜色的像素点在图中相连,因此需要改颜色的像素点为与起始像素点在同一个连通分量中的全部像素点,连通性问题可以使用并查集解决。
并查集初始化时,每个像素点分别属于不同的集合,每个集合只包含一个像素点。
初始化之后,遍历每个像素点,如果一个像素点的颜色与其上边或左边的相邻像素点的原色相同,则将两个相邻像素点所在的集合做合并。
完成合并操作之后,获得起始像素点所在的集合,再次遍历所有像素点,对于每个与起始像素点所在的集合相同的像素点,将颜色改为 color \textit{color} color。
完成图像渲染之后,返回 image \textit{image} image。
代码
java
class Solution {
public int[][] floodFill(int[][] image, int sr, int sc, int color) {
int originalColor = image[sr][sc];
if (originalColor == color) {
return image;
}
int m = image.length, n = image[0].length;
UnionFind uf = new UnionFind(m * n);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i > 0 && image[i][j] == image[i - 1][j]) {
uf.union(i * n + j, (i - 1) * n + j);
}
if (j > 0 && image[i][j] == image[i][j - 1]) {
uf.union(i * n + j, i * n + j - 1);
}
}
}
int sRoot = uf.find(sr * n + sc);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (uf.find(i * n + j) == sRoot) {
image[i][j] = color;
}
}
}
return image;
}
}
class UnionFind {
private int[] parent;
private int[] rank;
public UnionFind(int n) {
parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
rank = new int[n];
}
public void union(int x, int y) {
int rootx = find(x);
int rooty = find(y);
if (rootx != rooty) {
if (rank[rootx] > rank[rooty]) {
parent[rooty] = rootx;
} else if (rank[rootx] < rank[rooty]) {
parent[rootx] = rooty;
} else {
parent[rooty] = rootx;
rank[rootx]++;
}
}
}
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
}
复杂度分析
-
时间复杂度: O ( m n × α ( m n ) ) O(mn \times \alpha(mn)) O(mn×α(mn)),其中 m m m 和 n n n 分别是图像 image \textit{image} image 的行数和列数, α \alpha α 是反阿克曼函数。并查集的初始化需要 O ( m n ) O(mn) O(mn) 的时间,然后遍历 m n mn mn 个像素点,执行 O ( m n ) O(mn) O(mn) 次合并操作,这里的并查集使用了路径压缩和按秩合并,单次操作的时间复杂度是 O ( α ( m n ) ) O(\alpha(mn)) O(α(mn)),因此并查集初始化之后的操作的时间复杂度是 O ( m n × α ( m n ) ) O(mn \times \alpha(mn)) O(mn×α(mn)),总时间复杂度是 O ( m n + m n × α ( m n ) ) = O ( m n × α ( m n ) ) O(mn + mn \times \alpha(mn)) = O(mn \times \alpha(mn)) O(mn+mn×α(mn))=O(mn×α(mn))。
-
空间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是图像 image \textit{image} image 的行数和列数。并查集需要 O ( m n ) O(mn) O(mn) 的空间。