FloodFill算法——DFS

FloodFill算法就是用来寻找性质相同的连通快的算法,这篇博客都是用dfs来实现FloodFill算法

1.图像渲染

题目链接:733. 图像渲染 - 力扣(LeetCode)

题目解析:将和(sr,sc)相连的所有像素相同的点,将这些点的值都修改为目标值

算法讲解:

利用深搜,遍历所有与初始点相连的所有像素相同的点,然后将其修改成指定的像素值即可

代码实现:

一个小细节:如果color==image[sr][sc],此时就不用去遍历image了,此时直接返回image即可,或者也可以创建一个check的boolean类型的数组,去标记已经访问的位置

java 复制代码
//第一种写法
class Solution {
    int h,l,prev;
    int[] dx={0,0,1,-1};
    int[] dy={1,-1,0,0};
    public int[][] floodFill(int[][] image, int sr, int sc, int color) {
        h=image.length;
        l=image[0].length;
        prev=image[sr][sc];
        if(prev==color) return image;
        dfs(image,sr,sc,color);
        return image;
    }
    public void dfs(int[][] image,int i,int j,int color){
        image[i][j]=color;
        for(int k=0;k<4;k++){
            int x=i+dx[k];
            int y=j+dy[k];
            if(x>=0 && x<h && y>=0 && y<l && image[x][y]==prev){
                dfs(image,x,y,color);
            }
        }
    }
}

//第二种写法
class Solution {
    int h,l,prev;
    int[] dx={0,0,1,-1};
    int[] dy={1,-1,0,0};
    boolean[][] check;
    public int[][] floodFill(int[][] image, int sr, int sc, int color) {
        h=image.length;
        l=image[0].length;
        check=new boolean[h][l];
        prev=image[sr][sc];
        dfs(image,sr,sc,color);
        return image;
    }
    public void dfs(int[][] image,int i,int j,int color){
        image[i][j]=color;
        for(int k=0;k<4;k++){
            int x=i+dx[k];
            int y=j+dy[k];
            if(x>=0 && x<h && y>=0 && y<l && image[x][y]==prev && check[x][y]==false){
                check[x][y]=true;
                dfs(image,x,y,color);
                check[x][y]=false;
            }
        }
    }
}

2.岛屿数量

题目链接:200. 岛屿数量 - 力扣(LeetCode)

题目解析:计算岛屿的数量,也就是计算grid数组中数字为1的连通快的个数

算法讲解: FloodFill

首先要创建一个同等规模的check的boolean数组,先遍历一遍grid数组,当遇到1时且该对应的check[i][j]==false,就通过dfs递归函数将这个位置的上下左右中为1的位置的对应到check数组中的值改为true即可

代码实现:

java 复制代码
class Solution {
    boolean[][] check;
    int h,l;
    int[] dx={0,0,-1,1};
    int[] dy={-1,1,0,0};
    public int numIslands(char[][] grid) {
        h=grid.length;
        l=grid[0].length;
        int ret=0;
        check=new boolean[h][l];
        for(int i=0;i<h;i++){
            for(int j=0;j<l;j++){
                if(grid[i][j]=='1'&&check[i][j]==false){
                    ret++;
                    dfs(grid,i,j);
                }
            }
        }
        return ret;
    }
    public void dfs(char[][] grid,int i,int j){
        check[i][j]=true;
        for(int k=0;k<4;k++){
            int x=i+dx[k];
            int y=j+dy[k];
            if(x>=0 && x<h && y>=0 && y<l && check[x][y]==false && grid[x][y]=='1'){
                dfs(grid,x,y);
            }
        }
    }
}

3.岛屿的最大面积

题目解析:返回最大岛屿的面积

算法讲解:FloodFill

依旧是对grid数组做一遍深度优先遍历,当遇到1时,就上下左右去寻找连通的1,且创建一个全局变量area去记录每一次递归时找到的岛屿的面积

代码实现:

代码细节:每次递归之前要将area置为0,因为每一次递归都是去找新的岛屿,计算新的岛屿的面积之前,要将之前计算的岛屿的面积去掉,也就是将area置为0

java 复制代码
class Solution {
    boolean[][] vis;
    int h,l,area,ret;
    int[] dx={0,0,1,-1};
    int[] dy={1,-1,0,0};
    public int maxAreaOfIsland(int[][] grid) {
        h=grid.length;
        l=grid[0].length;
        vis=new boolean[h][l];
        for(int i=0;i<h;i++){
            for(int j=0;j<l;j++){
                if(grid[i][j]==1&&vis[i][j]==false){
                    area=0;//小细节
                    dfs(grid,i,j);
                    ret=Math.max(ret,area);
                }
            }
        }
        return ret;
    }
    public void dfs(int[][] grid,int i,int j){
        area++;
        vis[i][j]=true;
        for(int k=0;k<4;k++){
            int x=i+dx[k];
            int y=j+dy[k];
            if(x>=0 && x<h && y>=0 && y<l && grid[x][y]==1 && vis[x][y]==false){
                dfs(grid,x,y);
            }
        }
    }
}

4.被围绕的区域

题目链接:130. 被围绕的区域 - 力扣(LeetCode)

题目解析:将矩阵中的被X围绕的连通O区域中的字母全部改为字母X

算法讲解:FloodFill

这道题的难点就是在于处理位于边界的连通O区域,如果我们直接对矩阵进行一遍深度遍历,那马就要写两个dfs函数,一个用来处理内部的O区域,另一个用来处理处于边界的O区域,但是这样写是在是太复杂了

正难则反,,我们可以先去处理边界的情况,先将位于边界的连通O区域全部改为字符点,处理完边界情况之后,在去遍历矩阵,遇到字符点,则就将其还原成O,此时如果遇到O,那么此时的O区域肯定是合法的,此时直接将O改为X即可

代码实现:

java 复制代码
class Solution {
    int h,l;
    int[] dx={0,0,1,-1};
    int[] dy={1,-1,0,0};
    public void solve(char[][] board) {
        h=board.length;
        l=board[0].length;
        for(int j=0;j<l;j++){
            if(board[0][j]=='O') dfs(board,0,j);
            if(board[h-1][j]=='O') dfs(board,h-1,j);
        }
        for(int i=0;i<h;i++){
            if(board[i][0]=='O') dfs(board,i,0);
            if(board[i][l-1]=='O') dfs(board,i,l-1);
        }
        for(int i=0;i<h;i++){
            for(int j=0;j<l;j++){
                if(board[i][j]=='.') board[i][j]='O';
                else if(board[i][j]=='O') board[i][j]='X';
            }
        }
    }
    public void dfs(char[][] board,int i,int j){
        board[i][j]='.';
        for(int k=0;k<4;k++){
            int x=i+dx[k];
            int y=j+dy[k];
            if(x>=0 && x<h && y>=0 && y<l && board[x][y]=='O'){
                dfs(board,x,y);
            }
        }
    }
}

5.太平洋大西洋流水问题

题目链接:417. 太平洋大西洋水流问题 - 力扣(LeetCode)

题目解析:找出矩阵中既能流入太平洋和大西洋的位置

解法一:直接暴搜

直接遍历矩阵,直接对每一个位置进行一遍FloodFill,但是这样会出现重复走大量的重复路径,代码可能会超时

解法二:正难则反

我们可以转换一下思路,题目要求我们找出既能流入太平洋又能流入大西洋的位置,可以反过来思考,从太平洋或者大西洋能流入矩阵的哪一些位置,此时创建两个同等规模的boolean类型的数组paci和atla,如果paci[i][j]或者atla[i][j]的值为true,那么就代表从太平洋或者大西洋的水能流入(i,j)位置,当paci[i][j]和atla[i][j]的值同时为true时,就代表太平洋和大西洋的水都能能流入(i,j)位置

此时对分别对第一行和第一列,最后一行和最后一列,分别进行一遍FloodFill即可

代码实现:

java 复制代码
class Solution { 
    List<List<Integer>> ret;
    int h,l;
    int[] dx={0,0,1,-1};
    int[] dy={1,-1,0,0};
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        h=heights.length;
        l=heights[0].length;
        boolean[][] paci=new boolean[h][l];
        boolean[][] atla=new boolean[h][l];
        ret=new ArrayList<>();
        //处理太平洋
        //第一行
        for(int j=0;j<l;j++) dfs(heights,0,j,paci);
        //第一列
        for(int i=0;i<h;i++) dfs(heights,i,0,paci);
        //处理大西洋
        //最后一行
        for(int j=0;j<l;j++) dfs(heights,h-1,j,atla);
        //最后一列
        for(int i=0;i<h;i++) dfs(heights,i,l-1,atla);
        for(int i=0;i<h;i++){
            for(int j=0;j<l;j++){
                if(paci[i][j]==true&&atla[i][j]==true){
                    List<Integer> tmp=new ArrayList<>();
                    tmp.add(i);
                    tmp.add(j);
                    ret.add(tmp);
                }
            }
        }
        return ret;
    }
    public void dfs(int[][] heights,int i,int j,boolean[][] ocean){
        ocean[i][j]=true;
        for(int k=0;k<4;k++){
            int x=i+dx[k];
            int y=j+dy[k];
            if(x>=0 && x<h && y>=0 && y<l && heights[x][y]>=heights[i][j] && ocean[x][y]==false){
                dfs(heights,x,y,ocean);
            }
        }
    }
}

6.扫雷游戏

题目链接:529. 扫雷游戏 - 力扣(LeetCode)

题目解析:就是扫雷游戏的规则,点击一个为挖出的方块,则与这个空格相连的空格也会被展开,如果某一个空格的相邻有地雷的花,就不展开与这个空格相邻的空格,只需要将这个空格的值改为地雷的数量即可

算法讲解:FloodFill

这道题就是对棋盘从点击的位置进行一遍深度遍历即可,如果遇到为挖出的空格,则将其翻转即可,但是如果未挖出的空格相邻位置有地雷,将这个空格的值改为地雷的数量

则此时dfs函数就是用来翻转空格的,但是翻转的情况有两种,如果空格的相邻位置没有地雷,将M改为B即可,如果空格的相邻位置有地雷,要将M给为地雷的数量,所以此时在递归之前,要对地雷的数量进行一个判断

代码实现:

java 复制代码
class Solution {
    int h,l;
    int[] dx={0,0,-1,1,-1,-1,1,1};
    int[] dy={-1,1,0,0,-1,1,-1,1};
    public char[][] updateBoard(char[][] board, int[] click) {
        h=board.length;
        l=board[0].length;
        int x=click[0];
        int y=click[1];
        if(board[x][y]=='M'){
            board[x][y]='X';
            return board;
        }
        dfs(board,x,y);
        return board;
    }
    public void dfs(char[][] board,int i,int j){
        //判断地雷的数量
        int count=0;
        for(int k=0;k<8;k++){
            int x=i+dx[k];
            int y=j+dy[k];
            if(x>=0 && x<h && y>=0 && y<l && board[x][y]=='M'){
                count++;
            }
        }
        if(count==0){
            board[i][j]='B';
            for(int k=0;k<8;k++){
                int x=i+dx[k];
                int y=j+dy[k];
                if(x>=0 && x<h && y>=0 && y<l && board[x][y]=='E'){
                    dfs(board,x,y);
                }
            }
        }else{
            board[i][j]=(char)('0'+count);
        }
    }
}

7. 衣橱整理

题目链接:LCR 130. 衣橱整理 - 力扣(LeetCode)

题目描述:如上图

算法讲解:FloodFill

依旧是对m行n列的放个做一遍深度优先遍历即可

代码实现:

java 复制代码
class Solution {
    int ret;
    int h,l;
    boolean[][] vis;
    int[] dx={0,0,-1,1};
    int[] dy={-1,1,0,0};
    public int wardrobeFinishing(int m, int n, int t) {
        h=m;
        l=n;
        vis=new boolean[h][l];
        dfs(0,0,t);
        return ret;
    }
    public void dfs(int i,int j,int t){
        vis[i][j]=true;
        ret++;
        for(int k=0;k<4;k++){
            int x=i+dx[k];
            int y=j+dy[k];
            int tmp=(x/10)+(x%10)+(y/10)+(y%10);
            if(x>=0 && x<h && y>=0 && y<l && tmp<=t && vis[x][y]==false){
                dfs(x,y,t);
            }
        }
    }
}