泛洪填充(FloodFill)问题在图像处理中非常常用,它和连通图的概念相似。最近在YouTube看 Patrick Shyu (TechLead)的视频,他讲他在谷歌面试别人的时候,常会出一道泛洪填充的题。
这道题参考LeetCode733:
有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。
给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为新的颜色值。
最后返回经过上色渲染后的图像。
示例 1:
输入: image = [[1,1,1],[1,1,0],[1,0,1]] sr = 1, sc = 1, newColor = 2
输出: [[2,2,2],[2,2,0],[2,0,1]] 解析: 在图像的正中间,(坐标(sr,sc)=(1,1)),
在路径上所有符合条件的像素点的颜色都被更改成2。 注意,右下角的像素没有更改为2, 因为它不是在上下左右四个方向上与初始点相连的像素点。这里是引用
先说结论:泛洪填充目前已知有三种解决方法,但是要注意边界条件,分别是:
以下是自己对DFS和BFS在本地实现的结果:
package floodFill;
import java.util.LinkedList;
import java.util.Queue;
public class FloodFill {
public static void main(String[] args){
int[][] image = new int[][]{{1,1,1},{1,1,0},{1,0,1}};
floodFill_DFS(image, 1, 1, 2);
for (int[] list: image){
for (int e: list){
System.out.print(e + " ");
}
System.out.println();
}
}
//速度慢,但支持的image更大
public static int[][] floodFill_BFS(int[][] image, int sr, int sc, int newColor) {
//BFS
Queue<int[]> queue = new LinkedList<int[]>();
queue.offer(new int[]{sr, sc});
int curColor = image[sr][sc];
if (curColor == newColor)
return image;
while(!queue.isEmpty()){
int[] curXY = queue.poll();
int row = curXY[0];
int col = curXY[1];
if (image[row][col] == curColor){
image[row][col] = newColor;
if (row > 0)
queue.offer(new int[]{row-1,col});
if (row < image.length-1)
queue.offer(new int[]{row+1,col});
if (col > 0)
queue.offer(new int[]{row,col-1});
if (col < image[0].length-1)
queue.offer(new int[]{row,col+1});
}
}
return image;
}
//速度快,但可能栈溢出
public static int[][] floodFill_DFS(int[][] image, int sr, int sc, int newColor){
if (image[sr][sc] != newColor)
dfs(image, sr, sc, image[sr][sc], newColor);
return image;
}
private static void dfs(int[][] image, int r, int c, int oldColor, int newColor){
if (image[r][c] != oldColor){
return;
}else{
image[r][c] = newColor;
if (r > 0)
dfs(image,r-1,c,oldColor,newColor);
if (r < image.length-1)
dfs(image,r+1,c,oldColor,newColor);
if (c > 0)
dfs(image,r,c-1,oldColor,newColor);;
if (c < image[0].length-1)
dfs(image,r,c+1,oldColor,newColor);
}
}
}
LeetCode 994:
在给定的网格中,每个单元格可以有以下三个值之一:
值 0 代表空单元格; 值 1 代表新鲜橘子; 值 2 代表腐烂的橘子。 每分钟,任何与腐烂的橘子(在 4
个正方向上)相邻的新鲜橘子都会腐烂。返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。
解题思路:
> 一开始,我们找出所有腐烂的橘子,将它们放入队列,作为第 0 层的结点。
然后进行 BFS 遍历,每个结点的相邻结点可能是上、下、左、右四个方向的结点,注意判断结点位于网格边界的特殊情况。
由于可能存在无法被污染的橘子,我们需要记录新鲜橘子的数量。在 BFS 中,每遍历到一个橘子(污染了一个橘子),就将新鲜橘子的数量减一。如果 BFS 结束后这个数量仍未减为零,说明存在无法被污染的橘子。
class Solution {
public int orangesRotting(int[][] grid) {
//BSF
if (grid == null || grid.length == 0 || grid[0].length == 0)
return -1;
Queue<int[]> queue = new LinkedList<int[]>();
int m = grid.length; //rows
int n = grid[0].length; //cols
int count = 0; //count fresh one
int mins = 0; //count tranverse times, that is minutes
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++){
if (grid[i][j] == 1)
count++;
if (grid[i][j] == 2)
queue.offer(new int[]{i,j});
}
while(count > 0 && !queue.isEmpty()){
mins++;
int qSize = queue.size(); // all tranvers
while (qSize > 0){
qSize--;
int[] rc = queue.poll();
int r = rc[0];
int c = rc[1];
if(r-1 >= 0 && grid[r-1][c] == 1){
grid[r-1][c] = 2; //mark fresh rotten
queue.offer(new int[]{r-1, c});
count--;
}
if (r+1 < m && grid[r+1][c] == 1){
grid[r+1][c] = 2; //mark fresh rotten
queue.offer(new int[]{r+1, c});
count--;
}
if (c-1 >= 0 && grid[r][c-1] == 1){
grid[r][c-1] = 2; //mark fresh rotten
queue.offer(new int[]{r, c-1});
count--;
}
if (c+1 < n && grid[r][c+1] == 1){
grid[r][c+1] = 2; //mark fresh rotten
queue.offer(new int[]{r, c+1});
count--;
}
}
}
if (count > 0)
return -1;
return mins;
}
}
给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。
示例 1: 输入:
0 0 0 0 1 0 0 0 0 输出:
0 0 0 0 1 0 0 0 0 示例 2: 输入:
0 0 0 0 1 0 1 1 1 输出:
0 0 0 0 1 0 1 2 1
class Solution {
public int[][] updateMatrix(int[][] matrix) {
if (matrix == null || matrix.length == 0)
return matrix;
int[] dx = new int[]{1, -1, 0, 0};
int[] dy = new int[]{0, 0, -1, 1}; //定义坐标四周移位向量
Queue<int[]> queue = new LinkedList();
int m = matrix.length;
int n = matrix[0].length;
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++){
if (matrix[i][j] == 0)
queue.offer(new int[]{i, j});
else
matrix[i][j] = -1; //将未访问的1标记为-1,同时为了满足计数
}
while (!queue.isEmpty()){
int[] point = queue.poll();
for (int i = 0; i < 4; i++){
int x = point[0] + dx[i];
int y = point[1] + dy[i];
if (x >= 0 && x < m &&
y >= 0 && y < n && matrix[x][y] == -1){
matrix[x][y] = matrix[point[0]][point[1]] + 1;
queue.offer(new int[]{x, y});
}
}
}
return matrix;
}
}
你现在手里有一份大小为 N x N 的「地图」(网格) grid,上面的每个「区域」(单元格)都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,请你找出一个海洋区域,这个海洋区域到离它最近的陆地区域的距离是最大
的。我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个区域之间的距离是
|x0 - x1| + |y0 - y1| 。如果我们的地图上只有陆地或者海洋,请返回 -1。
输入:[[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释:
海洋区域 (1, 1) 和所有陆地区域之间的距离都达到最大,最大距离为 2。
class Solution {
public int maxDistance(int[][] grid) {
if (grid == null || grid.length == 0)
return -1;
int[] dx = new int[]{1, -1, 0, 0};
int[] dy = new int[]{0, 0, 1, -1};
Queue<int[]> queue = new LinkedList<>();
int m = grid.length; //row
int n = grid[0].length; //col
// add all ground to queue
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++){
if (grid[i][j] == 1)
queue.offer(new int[]{i, j});
}
boolean hasZero = false;
int[] point = null;
while (!queue.isEmpty()){
point = queue.poll();
for (int i = 0; i < 4; i++){
int x = point[0] + dx[i];
int y = point[1] + dy[i];
//add ocean around point
if (x < 0 || x >= m ||
y < 0 || y >= n || grid[x][y] != 0){
continue;
}
grid[x][y] = grid[point[0]][point[1]] + 1;
hasZero = true;
queue.offer(new int[]{x, y});
}
}
if (point == null || !hasZero)
return -1;
return grid[point[0]][point[1]] - 1;
}
}
泛洪填充的题还有*: