0092 经典算法系列——泛洪填充(FloodFill)

泛洪填充(FloodFill)问题在图像处理中非常常用,它和连通图的概念相似。最近在YouTube看 Patrick Shyu (TechLead)的视频,他讲他在谷歌面试别人的时候,常会出一道泛洪填充的题。

1、泛洪填充——图像上色

这道题参考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, 因为它不是在上下左右四个方向上与初始点相连的像素点。

这里是引用

先说结论:泛洪填充目前已知有三种解决方法,但是要注意边界条件,分别是:

  1. 深度优先遍历DFS:通过不断的遍历当前点的【上、下、左、右】点,相同则修改(连通),不断递归DFS特点:速度快,但是对于图特别大的场景可能栈溢出
  2. 广度优先遍历BSF:BFS通常与队列结合使用,本方法是将当前点加入队列,然后再将其【上、下、左、右】相邻点加入队尾。从队头弹出(poll-取值并删除)节点,如果与旧值相等则修改为新值,重复以上步骤,直到队列为空。BFS特点:对图很大的场景适用,但速度慢
  3. 并查集:从(0, 0)位置开始依次遍历,这时就不需要同时兼顾上下左右四个方向了,只需要看看它右边和下面的像素点颜色是不是和我一样都为color,一样就合并。不一样就不管它,让它自己单独作为一个集合。最后渲染指定的一个集合即可。

以下是自己对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);
        }

    }
}

运行结果:
0092 经典算法系列——泛洪填充(FloodFill)_第1张图片

2、泛洪填充——腐烂的橘子


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;
        
    }
}

3、542.01 矩阵

给定一个由 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;

    }
}

1162.地图分析

你现在手里有一份大小为 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;

    }
}

泛洪填充的题还有*:

  • 1020. 飞地的数量
  • 1254. 统计封闭岛屿的数目
  • 547. 朋友圈
  • 200.岛屿数量

你可能感兴趣的:(Algorithm)