自学内容网 自学内容网

专题十二_floodfill(洪水灌溉)算法_算法专题详细总结

目录

1. 图像渲染(medium)

解析:

函数头:

函数体:固定模板 

设置全局变量:

总结:

2. 岛屿数量(medium)

解析:

注意:

总结:

3. 岛屿的最⼤⾯积(medium)

解析:

解决办法:

总结:

4. 被围绕的区域(medium)

解析:

总结:

5. 太平洋⼤西洋⽔流问题(medium)

解析:

方法一:

方法二:

那么就分别设置两个bool二维矩阵:

进行dfs设置,如果能够进入当前块的位置,说明当前海洋能流向这个位置:

最后就是判断两个bool数组是不是会有重复的数据块,判断两个数据块是否同时为true

总结:

6. 扫雷游戏(medium)

解析:

总结:

7. 衣橱整理(medium)

解析:

设置全局变量:

那么就是固定的深度优先遍历算法:

总结:


有过专题十一的练习,不说这个专题易如反掌吧,简直就是手拿把掐,但是还是有些小细节需要注意,我们一起来看看吧:

1. 图像渲染(medium)

题目意思贼简单,就是说给我们一个中心元素sr,sc然后就把它周围所有的跟中心元素相同的元素就全都进行改变颜色变成color。

解析:

floodfill 一眼就是dfs,然后直接对(sr,sc)这个坐标进行dfs遍历 ,只需要遍历一次,将所有跟这个坐标相通的元素全部都进行修改color即可

那么:

函数头:

dfs(nums,sr,sc);

函数体:固定模板 

for(int k=0;k<4;k++)

        {

            int x=i+dx[k];

            int y=j+dy[k];

            if(x>=0&&x<m&&y>=0&&y<n&&image[x][y]==ret&&visit[x][y]==false)

            {

                visit[x][y]=true;

                image[x][y]=color;

                dfs(image,x,y);

            }

        }

设置全局变量:

    int dx[4]={0,-1,0,1};  
    int dy[4]={-1,0,1,0};  //这里要遍历当前位置的上下左右四个位置,如果满足if里面的条件,就可以递归到下一层
    int visit[100][100];  //设置当前位置是否被访问过,不会出现死循环的条件

class Solution {
public:
    int dx[4]={0,-1,0,1};
    int dy[4]={-1,0,1,0};
    int visit[100][100];
    int color,n,m,sr,sc;
    int ret;
    vector<vector<int>> floodFill(vector<vector<int>>& image, int _sr, int _sc, int _color) {
        color=_color;
        m=image.size(),n=image[0].size(),sr=_sr,sc=_sc;
        visit[sr][sc]=true;
        ret=image[sr][sc];
        image[sr][sc]=color;
        dfs(image,sr,sc);
        return image;
    }

    void dfs(vector<vector<int>>& image,int i,int j)
    {
        for(int k=0;k<4;k++)
        {
            int x=i+dx[k];
            int y=j+dy[k];
            if(x>=0&&x<m&&y>=0&&y<n&&image[x][y]==ret&&visit[x][y]==false)
            {
                visit[x][y]=true;
                image[x][y]=color;
                dfs(image,x,y);
            }
        }
    }
};

总结:

这就跟上一个专题的那个深度优先遍历二维矩阵的方法一模一样,简直手拿把掐,就是设置全局变量,然后再一个点开始直接进行递归一次,出来即可。

2. 岛屿数量(medium)

题目意思很简单,就是看有几个 1是联通的就是说明分几块区域。

解析:

这就真的很简单了,就一眼dfs,直接秒杀:

思路肯定就是两次for()然后遍历每一个点,就从当前点如果既是‘1’ 又是没有被访问过的,那么这个时候就要加上bool数组了

bool visit[300][300];

注意:

此时从主函数进去的时候,就是要将当前的visit设置成true表示已经被访问过了,这样不会出现是循环的问题,那么就在这个双重for内进行ret++,因为每次进入dfs,当前块的岛屿的所有1都会被置为true,所以只要能进入一次dfs说明就是一块岛屿。

后面就是我们熟悉的固定套路了,回溯+递归。

void dfs(vector<vector<char>>& grid,int i,int j,vector<vector<bool>>& vis)

    {

        vis[i][j]=true;

        for(int k=0;k<4;k++)

        {

            int x=dx[k]+i,y=dy[k]+j;

            if(x>=0&&x<m&&y>=0&&y<n&&vis[x][y]==false&&grid[x][y]=='1')

            {

                dfs(grid,x,y,vis);

            }

        }

    }

class Solution {
public:
    int dx[4]={1,-1,0,0},dy[4]={0,0,-1,1};
    int m,n,ret;

    int numIslands(vector<vector<char>>& grid) {
        m=grid.size(),n=grid[0].size();
        vector<vector<bool>> vis(m,vector<bool>(n));
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(vis[i][j]==false&&grid[i][j]=='1')
                {
                    ret++;
                    dfs(grid,i,j,vis);
                }
            }
        }    

        return ret;
    }

    void dfs(vector<vector<char>>& grid,int i,int j,vector<vector<bool>>& vis)
    {
        vis[i][j]=true;

        for(int k=0;k<4;k++)
        {
            int x=dx[k]+i,y=dy[k]+j;

            if(x>=0&&x<m&&y>=0&&y<n&&vis[x][y]==false&&grid[x][y]=='1')
            {
                dfs(grid,x,y,vis);
            }
        }
    }
};

总结:

这个是一道狠狠狠经典的floodfill的题目,一定要做会做熟做透!~

3. 岛屿的最⼤⾯积(medium)

题目意思很简单,但是这题跟上面几题有那么一点点的不一样,一定要思考到位。

解析:

要求的是所有岛屿里面面积最大的那一块岛屿的面积。那么就是跟求岛屿的数量是不一样的。

我开始做的的时候还是按照以前的套路写下来,可是一直报错,说我面积会少1,这证明,再按照上面的写法的时候,只有满足我当前位置的四周有‘1’才能进入if语句进行+1,但是当我到最后一块的时候,我的四周全是被置为“true” 的字符‘1’,这样最后一块就进不去if()语句,那么就会少算一块面积。

解决办法:

那么就是再遍历所有岛屿的时候,计算面积的唯一办法就是只要进入dfs那么sum就++,这样就会让每次进入dfs的每一块面积都被计算上,保证不会漏掉。

那么这样随之visit这个数组也要随着改变,当前的位置就要跟着变成true

class Solution {
public:
    int dx[4]={0,-1,0,1};
    int dy[4]={-1,0,1,0};
    bool visit[100][100];
    int ret=0,m,n,sum;

    int maxAreaOfIsland(vector<vector<int>>& grid) {
        m=grid.size(),n=grid[0].size();
        for(int i=0;i<m;i++)
            for(int j=0;j<n;j++)
            {
                if(grid[i][j]==1&&visit[i][j]==false)
                {
                    sum=0;
                    visit[i][j]=true;
                    dfs(grid,i,j);
                    ret=max(ret,sum);
                }
            }

        return ret;
    }
    void dfs(vector<vector<int>>& grid,int i,int j)
    {
        sum++;
        visit[i][j]=true;

        for(int k=0;k<4;k++)
        {
            int x=dx[k]+i;
            int y=dy[k]+j;
            if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==1&&visit[x][y]==false)
            {
                dfs(grid,x,y);
            }
        }
    }
};

总结:

就是要专门总结这一题跟上面一题的区别,这题是求岛屿的面积,那么就要单独把计算拿出来进行计算;上面一题只是计算岛屿的数量,那么就只需要计算能进入岛屿几次就ok了

4. 被围绕的区域(medium)

题目意思就是说,只要有岛屿‘O’跟边缘相连,就活着,其它没有跟边缘相联的岛屿全被杀掉变成‘X’

解析:

我觉得我刚开始做出这题的办法有点蠢,但是我觉得我的思路是没有问题的:

题目要我们找到整个地图‘O’岛屿没有跟边缘相连的岛屿,然后把他全变成‘X’,这样我就逆向思维,找到所有已经再边缘存在的岛屿,然后dfs遍历它,把岛屿就置为true,然后最后来一次双重for遍历整个二维数组,这样就可以找到还是false的岛屿直接变成‘X’即可。

class Solution {
public:
    int dx[4]={0,-1,0,1};
    int dy[4]={-1,0,1,0};
    bool visit[250][250];
    int n,m;
    void solve(vector<vector<char>>& board) {
        m=board.size(),n=board[0].size();
        for(int i=0;i<n;i++)
        {
            if(board[0][i]=='O'&&visit[0][i]==false)
            {
                visit[0][i]=true;
                dfs(board,0,i);
            }
            if(board[m-1][i]=='O'&&visit[m-1][i]==false)
            {
                visit[m-1][i]=true;
                dfs(board,m-1,i);
            }
        }
        for(int i=0;i<m;i++)
        {
            if(board[i][0]=='O'&&visit[i][0]==false)
            {
                visit[i][0]=true;
                dfs(board,i,0);
            }
            if(board[i][n-1]=='O'&&visit[i][n-1]==false)
            {
                visit[i][n-1]=true;
                dfs(board,i,n-1);
            }
        }

        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(board[i][j]=='O'&&visit[i][j]==false) board[i][j]='X';
            }
        }
    }

    void dfs(vector<vector<char>>& board,int i,int j)
    {
        for(int k=0;k<4;k++)
        {
            int x=dx[k]+i;
            int y=dy[k]+j;
            if(x>=0&&x<m&&y>=0&&y<n&&visit[x][y]==false&&board[x][y]=='O')
            {
                visit[x][y]=true;
                dfs(board,x,y);
            }
        }
    }
};

总结:

相通了还是很简单的,都是一样的问题,没有什么区别,都是dfs深度优先遍历后,利用visit数组来设置当前是否要访问。

5. 太平洋⼤西洋⽔流问题(medium)

题目意思有点难理解,就是说再二维矩阵里面如果有一个数字足够大,那么他就能流向其他比他小的方块上,知道这个方块上的水能流向pac和atl两个海洋的话,就把他单独添加进行返回。

解析:

方法一:

我开始做的时候,使考虑到遍历每一个方块进行dfs向四周进行扩散,如果能够扩散到pac和atl两个海洋就可以进行添加,但是这样我就要判断当前有没有递归到这两个海洋,我就要把这个二维矩阵分别加上两行和两列,对这个矩阵进行遍历把新增的这两行分别当作pac和atl,分别设置-1,-2,那么就只要当这个二维矩阵遍历到-1,或者 -2 那么就讲当前海洋的bool置为true

那么这种方法肯定很无脑还空间复杂度贼高,还要保证两个洋的bool left right同时为true,这种代码写出来了,但是我没有过测试用例。
时间复杂度太高,会有太多重复的路径要走,最后肯定会超时。

方法二:

那么就要逆向思考,因为要从二维矩阵的每一个数字开始进行dfs,知道能够流到哪个海洋,不妨进行逆向思维,考虑这个二维矩阵的每一个边界上的方块能够网上爬到哪里的最高位置,分别对这两个海洋设置两个bool同样大的二维数组,直到这两个bool二维数组能够出现重复的被置为true的方块,就说明当前块的值,能够流向两个海洋。

那么就分别设置两个bool二维矩阵:

        vector<vector<bool>> pac(m,vector<bool>(n));    
        vector<vector<bool>> atl(m,vector<bool>(n));

进行dfs设置,如果能够进入当前块的位置,说明当前海洋能流向这个位置:

    void dfs(vector<vector<int>>& heights,vector<vector<bool>>& vis,int i,int j)
    {
        vis[i][j]=true;

        for(int k=0;k<4;k++)
        {
            int x=dx[k]+i;
            int y=dy[k]+j;
            if(x>=0&&x<m&&y>=0&&y<n&&vis[x][y]==false&&heights[x][y]>=heights[i][j])
            {
                dfs(heights,vis,x,y);
            }
        }
    }

最后就是判断两个bool数组是不是会有重复的数据块,判断两个数据块是否同时为true

 if(pac[i][j]&&atl[i][j]) ret.push_back({i,j});

class Solution {
public:
    int dx[4]={0,-1,0,1};
    int dy[4]={-1,0,1,0};
    int n,m;

    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        m=heights.size(),n=heights[0].size();
        vector<vector<bool>> pac(m,vector<bool>(n));    
        vector<vector<bool>> atl(m,vector<bool>(n));
        vector<vector<int>> ret;

        for(int i=0;i<n;i++) dfs(heights,pac,0,i);
        for(int i=0;i<m;i++) dfs(heights,pac,i,0);

        for(int i=0;i<n;i++) dfs(heights,atl,m-1,i);
        for(int i=0;i<m;i++) dfs(heights,atl,i,n-1);

        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(pac[i][j]&&atl[i][j]) ret.push_back({i,j});
            }
        }   
        return ret;
    }

    void dfs(vector<vector<int>>& heights,vector<vector<bool>>& vis,int i,int j)
    {
        vis[i][j]=true;

        for(int k=0;k<4;k++)
        {
            int x=dx[k]+i;
            int y=dy[k]+j;
            if(x>=0&&x<m&&y>=0&&y<n&&vis[x][y]==false&&heights[x][y]>=heights[i][j])
            {
                dfs(heights,vis,x,y);
            }
        }
    }
};

总结:

其实写出来并不难,主要就是要考虑用哪种角度来实现这种代码,具体还是跟上面几题没什么太大区别。
所谓正难则反,在dfs()体现的很好,只要正的方向考虑很麻烦,那么就可以考虑换个反方向进行思考。

6. 扫雷游戏(medium)

系统给出一个点击的位置click=[x,y] 然后寻找对应的坐标进行点击,如果点击的位子周围没有地雷,就全部都将‘E’ 置为 'B' 并且进行递归式的展开,直到递归式展开结束或者点击即使地雷就进行返回;当展开的过程中,当前位置附近有几颗地雷,当前位置就讲字符改为数字几。

解析:

题目意思理解清楚后就是说,系统给一个点击的坐标click[0,1];然后这个点击的坐标进行疯狂的递归式的展开,然后判断当前位置的附近八个放向是否会存在地雷,如果存在就说明当前位置被点击后要变成数字,那么就要定义一个sum变量来统计当前位置附近所有地雷的个数。

        for(int k=0;k<8;k++)
        {
            int x=dx[k]+i;
            int y=dy[k]+j;
            if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]=='M') sum++;
        }

这里遍历当前位置的八个方向,来统计地雷的个数,如果当前位置附近有地雷就再就改成地雷的个数。然后就要进行返回上一层

如果没有地雷,那么就可以递归式进入下一层,判断当前位置的附近位置是个什么情况

if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]=='E'&&visit[x][y]==false)

这里仍然要进行判断,下一个位置是没有被访问过,且visit是false的情况下,下一个位置是‘E’ ,跟之前的题目没有什么太大的变化。

唯一要注意的就是开头,系统给出的点击位置,当该位置就是地雷的时候,就直接结束游戏,并且将该位置置为‘X’。

class Solution {
public:
    bool visit[60][60];
    int dx[8]={-1,-1,-1,0,0,1,1,1};
    int dy[8]={-1,0,1,-1,1,-1,0,1};
    int n,m;

    vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) {
        m=board.size(),n=board[0].size();
        if(board[click[0]][click[1]]=='M') 
        {
            board[click[0]][click[1]]='X';
            return board;
        }
        dfs(board,click[0],click[1]);
        return board;
    }

    void dfs(vector<vector<char>>& board,int i,int j)
    {
        int sum=0;
        for(int k=0;k<8;k++)
        {
            int x=dx[k]+i;
            int y=dy[k]+j;
            if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]=='M') sum++;
        }
        if(sum!=0) board[i][j]=sum+'0';
        else
        {
            board[i][j]='B';
            for(int k=0;k<8;k++)
            {
                int x=dx[k]+i;
                int y=dy[k]+j;
                if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]=='E'&&visit[x][y]==false)
                {
                    dfs(board,x,y);
                }
            }
        }
    }
};

总结:

模拟类型 dfs 题⽬。
⾸先要搞懂题⽬要求,也就是游戏规则。
从题⽬所给的点击位置开始,根据游戏规则,来⼀次 dfs 即可。

7. 衣橱整理(medium)

题目意思比较简单,但是还是有点难读懂,意思就是给定义一个k值,然后要你从[0,0]开始进行遍历,可以上下左右进行遍历,判断当前所在位置的下标的每一位和是否大于k,如果大于,那么就要进行回退了,否则,就可以一直遍历下去,然后统计满足条件的格子。

解析:

题目还真的挺简单的,就是只需要从[0,0]开始只进行一次dfs即可,也不用恢复现场,那么

设置全局变量:

ret,n,m,k

bool visit[][];

int dx[],dy[]

然后dfs(0,0)开始进行遍历,只要进入这个函数,就要设置visit为true,让ret++,说明此时已经满足了这个条件。

那么就是固定的深度优先遍历算法:

 if(x>=0&&x<m&&y>=0&&y<n&&visit[x][y]==false&&check(x,y)<k)

唯一要注意的判断条件就是check()函数,里面要求出x,y两个数字的所有位数之和。判断是小于k的才能进入下一层。 

class Solution {
public:
    int ret=0;
    int n,m,k;
    int dx[4]={0,-1,0,1};
    int dy[4]={-1,0,1,0};
    bool visit[110][110];
    int wardrobeFinishing(int _m, int _n, int _k) {
        m=_m,n=_n,k=_k;
        dfs(0,0);
        return ret;
    }

    void dfs(int i,int j)
    {
        visit[i][j]=true;
        ret++;

        for(int k=0;k<4;k++)
        {
            int x=dx[k]+i;
            int y=dy[k]+j;
            if(x>=0&&x<m&&y>=0&&y<n&&visit[x][y]==false&&check(x,y)<k)
            {
                dfs(x,y);
            }
        }
    }

    int check(int i,int j)
    {
        int sum=0;
        while(i)
        {
            sum+=i%10;
            i/=10;
        }
        while(j)
        {
            sum+=j%10;
            j/=10;
        }
        return sum;
    }
};

总结:

题目并不难,只要能读懂意思,我相信肯定就能写出来。

总结到这里~确实发现了自己的成长,虽然不是说一帆风顺,但总归是有结果发生,对我的收获很大,希望对你也是~


原文地址:https://blog.csdn.net/2301_80636070/article/details/142994593

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!