专题四:FloodFill 算法

> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:了解什么是FloodFill 算法,并且FloodFill 算法。

> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!

> 专栏选自:递归、搜索与回溯算法_დ旧言~的博客-CSDN博客

> 望小伙伴们点赞👍收藏✨加关注哟💕💕

一、算法讲解

FloodeFill算法即填充算法,原理就是从一个点开始向四周扩散,向周围可以走到的点填充颜色,直到将可扩散到的点全部填充颜色。

通常使用下面两种方法解决FloodFill算法问题:

  • BFS (宽搜)算法通常使用队列实现,将起始像素点加入队列中,并不断扩展队列中的像素点,直到队列为空为止。
  • DFS(深搜) 算法通常使用递归实现,在处理当前像素点的相邻像素点时,递归调用 DFS 函数,不断深入直到无法找到相邻像素为止。

二、算法习题


2.1 第一题

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

题目描述:

算法思路:

可以利⽤「深搜」或者「宽搜」,遍历到与该点相连的所有「像素相同的点」,然后将其修改成指定的像素即可。

递归函数设计:

• 参数:

  1. 原始矩阵;
  2. 当前所在的位置;
  3. 需要修改成的颜⾊。 

• 函数体:

  1. a. 先将该位置的颜⾊改成指定颜⾊(因为我们的判断,保证每次进⼊递归的位置都是需要修改的位置);
  2. b. 遍历四个⽅向上的位置:
    1.  如果当前位置合法,并且与初试颜⾊相同,就递归进去。

代码呈现:

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

public:
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc,int color) 
    {
        if (image[sr][sc] == color)
            return image;
        m = image.size(), n = image[0].size();
        prev = image[sr][sc];
        dfs(image, sr, sc, color);
        return image;
    }
    void dfs(vector<vector<int>>& image, int i, int j, int color) 
    {
        image[i][j] = color;
        for (int k = 0; k < 4; k++) 
        {
            int x = i + dx[k], y = j + dy[k];
            if (x >= 0 && x < m && y >= 0 && y < n && image[x][y] == prev)
                dfs(image, x, y, color);
        }
    }
};

2.2 第二题

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

题目描述:

  

算法思路:

遍历整个矩阵,每次找到「⼀块陆地」的时候:

  • 说明找到「⼀个岛屿」,记录到最终结果 ret ⾥⾯;
  • 并且将这个陆地相连的所有陆地,也就是这块「岛屿」,全部「变成海洋」。这样的话,我们下次遍历到这块岛屿的时候,它「已经是海洋」了,不会影响最终结果。
  • 其中「变成海洋」的操作,可以利⽤「深搜」和「宽搜」解决,其实就是 733. 图像渲染 这道题~这样,当我们,遍历完全部的矩阵的时候, ret 存的就是最终结果。

算法流程:

  • 初始化 ret = 0 ,记录⽬前找到的岛屿数量;
  • 双重循环遍历⼆维⽹格,每当遇到⼀块陆地,标记这是⼀个新的岛屿,然后将这块陆地相连的陆地全部变成海洋。

递归函数的设计:

1. 把当前格⼦标记为⽔;
2. 向上、下、左、右四格递归寻找陆地,只有在下标位置合理的情况下,才会进⼊递归:

  • 下⼀个位置的坐标合理。
  • 并且下⼀个位置是陆地。

代码呈现:

class Solution {
    vector<vector<bool>> vis;
    int m, n;

public:
    int numIslands(vector<vector<char>>& grid) 
    {
        m = grid.size(), n = grid[0].size();
        vis = vector<vector<bool>>(m, vector<bool>(n));
        int ret = 0;
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++) 
            {
                if (!vis[i][j] && grid[i][j] == '1') 
                {
                    ret++;
                    dfs(grid, i, j);
                }
            }
        return ret;
    }
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
    void dfs(vector<vector<char>>& grid, int i, int j) 
    {
        vis[i][j] = true;
        for (int k = 0; k < 4; k++) 
        {
            int x = i + dx[k], y = j + dy[k];
            if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] &&grid[x][y] == '1') 
                dfs(grid, x, y);
        }
    }
};

2.3 第三题

题目链接:695. 岛屿的最大面积 - 力扣(LeetCode)

题目描述:

  

算法思路:

  • 遍历整个矩阵,每当遇到⼀块⼟地的时候,就⽤「深搜」或者「宽搜」将与这块⼟地相连的「整个岛屿」的⾯积计算出来。
  • 然后在搜索得到的「所有的岛屿⾯积」求⼀个「最⼤值」即可。
  • 在搜索过程中,为了「防⽌搜到重复的⼟地」:
    •  可以开⼀个同等规模的「布尔数组」,标记⼀下这个位置是否已经被访问过;
    • 也可以将原始矩阵的 1 修改成 0 ,但是这样操作会修改原始矩阵。

算法流程:

• 主函数内:

  1.  遍历整个数组,发现⼀块没有遍历到的⼟地之后,就⽤ dfs ,将与这块⼟地相连的岛屿的⾯积求出来;
  2. 然后将⾯积更新到最终结果 ret 中。 

• 深搜函数 dfs 中:

  1. 能够进到 dfs 函数中,说明是⼀个没遍历到的位置;
  2. 标记⼀下已经遍历过,设置⼀个变量 S = 1 (当前这个位置的⾯积为 1 ),记录最终的⾯积;
  3. 上下左右遍历四个位置: 如果找到⼀块没有遍历到的⼟地,就将与这块⼟地相连的岛屿⾯积累加到 S 上。
  4. 循环结束后, S 中存的就是整块岛屿的⾯积,返回即可。

代码呈现:

class Solution 
{
    bool vis[51][51];
    int m, n;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int count;

public:
    int maxAreaOfIsland(vector<vector<int>>& grid) 
    {
        m = grid.size(), n = grid[0].size();
        int ret = 0;
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                if (!vis[i][j] && grid[i][j] == 1) 
                {
                    count = 0;
                    dfs(grid, i, j);
                    ret = max(ret, count);
                }
        return ret;
    }
    void dfs(vector<vector<int>>& grid, int i, int j) 
    {
        count++;
        vis[i][j] = true;
        for (int k = 0; k < 4; k++) 
        {
            int x = i + dx[k], y = j + dy[k];
            if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] == 1)
                dfs(grid, x, y);
        }
    }
};

2.4 第四题

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

题目描述:

  

算法思路:

正难则反。可以先利⽤ dfs 将与边缘相连的 '0' 区域做上标记,然后重新遍历矩阵,将没有标记过的 '0'修改成 'X' 即可。

代码呈现:

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

public:
    void solve(vector<vector<char>>& board) 
    {
        m = board.size(), n = board[0].size();
        // 1. 把边界的 O 相连的联通块,全部修改成 .
        for (int j = 0; j < n; j++) {
            if (board[0][j] == 'O')
                dfs(board, 0, j);
            if (board[m - 1][j] == 'O')
                dfs(board, m - 1, j);
        }
        for (int i = 0; i < m; i++) 
        {
            if (board[i][0] == 'O')
                dfs(board, i, 0);
            if (board[i][n - 1] == 'O')
                dfs(board, i, n - 1);
        }
        // 2. 还原
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
            {
                if (board[i][j] == '.')
                    board[i][j] = 'O';
                else if (board[i][j] == 'O')
                    board[i][j] = 'X';
            }
    }
    void dfs(vector<vector<char>>& board, int i, int j) 
    {
        board[i][j] = '.';
        for (int k = 0; k < 4; k++) 
        {
            int x = i + dx[k], y = j + dy[k];
            if (x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'O') 
                dfs(board, x, y);
        }
    }
};

2.5 第五题

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

题目描述:

  

算法思路:

正难则反。

  • 如果直接去判断某⼀个位置是否既能到⼤西洋也能到太平洋,会重复遍历很多路径。
  • 我们反着来,从⼤西洋沿岸开始反向 dfs ,这样就能找出那些点可以流向⼤西洋;同理,从太平洋沿岸也反向 dfs ,这样就能找出那些点可以流向太平洋。那么,被标记两次的点,就是我们要找的结果。

代码呈现:

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

public:
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& h)
    {
        m = h.size(), n = h[0].size();
        vector<vector<bool>> pac(m, vector<bool>(n));
        vector<vector<bool>> atl(m, vector<bool>(n));
        // 1. 先处理 pac 洋
        for (int j = 0; j < n; j++)
            dfs(h, 0, j, pac);
        for (int i = 0; i < m; i++)
            dfs(h, i, 0, pac);
        // 2. 处理 atl 洋
        for (int i = 0; i < m; i++)
            dfs(h, i, n - 1, atl);
        for (int j = 0; j < n; j++)
            dfs(h, m - 1, j, atl);
        vector<vector<int>> ret;
        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>>& h, int i, int j, vector<vector<bool>>& vis) 
    {
        vis[i][j] = true;
        for (int k = 0; k < 4; k++) 
        {
            int x = i + dx[k], y = j + dy[k];
            if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && h[x][y] >= h[i][j])
                dfs(h, x, y, vis);
        }
    }
};

2.6 第六题

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

题目描述:

  

算法思路:

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

代码呈现:

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

public:
    vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) 
    {
        m = board.size(), n = board[0].size();
        int x = click[0], y = click[1];
        if (board[x][y] == 'M') // 直接点到地雷
        {
            board[x][y] = 'X';
            return board;
        }
        dfs(board, x, y);
        return board;
    }
    void dfs(vector<vector<char>>& board, int i, int j) 
    {
        // 统计⼀下周围的地雷个数
        int count = 0;
        for (int k = 0; k < 8; k++) 
        {
            int x = i + dx[k], y = j + dy[k];
            if (x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'M') 
                count++;
        }
        if (count) // 周围有地雷
        {
            board[i][j] = count + '0';
            return;
        } else // 周围没有地雷
        {
            board[i][j] = 'B';
            for (int k = 0; k < 8; k++) 
            {
                int x = i + dx[k], y = j + dy[k];
                if (x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'E')
                    dfs(board, x, y);
            }
        }
    }
};

2.7 第七题

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

题目描述:

  

算法思路:

  • 我们可以通过「深搜」或者「宽搜」,从 [0, 0] 点出发,按照题⽬的「规则」⼀直往 [m - 1,n - 1] 位置⾛。
  • 同时,设置⼀个全局变量。每次⾛到⼀个合法位置,就将全局变量加⼀。当我们把所有能⾛到的路都⾛完之后,全局变量⾥⾯存的就是最终答案。

算法流程:

• 递归函数设计:
a. 参数:当前所在的位置 [i, j] ,⾏⾛的边界 [m, n] ,坐标数位之和的边界 k ;

b. 递归出⼝:

  1. [i, j] 的坐标不合法,也就是已经超出能⾛的范围;
  2. [i, j] 位置已经⾛过了(因此我们需要创建⼀个全局变量 bool st[101][101] ,来标记当前位置是否⾛过);
  3. [i, j] 坐标的数位之和⼤于 k ; 

     上述情况的任何⼀种都是递归出⼝。

c. 函数体内部:

  1.  如果这个坐标是合法的,就将全局变量 ret++ ;
  2. 然后标记⼀下 [i, j] 位置已经遍历过;
  3. 然后去 [i, j] 位置的上下左右四个⽅向去看看。 

• 辅助函数:
a. 检测坐标 [i, j] 是否合法;
b. 计算出 i,j 的数位之和,然后与 k 作⽐较即可。

• 主函数:
a. 调⽤递归函数,从 [0 ,0] 点出发。

• 辅助的全局变量:

a. ⼆维数组 bool st[101][101] :标记 [i, j] 位置是否已经遍历过;
b. 变量 ret :记录⼀共到达多少个合法的位置。
c. 上下左右的四个坐标变换。

代码呈现:

class Solution {
    int m, n, k;
    bool vis[101][101];
    int ret;
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};

public:
    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) 
    {
        ret++;
        vis[i][j] = true;
        for (int k = 0; k < 4; k++) 
        {
            int x = i + dx[k], y = j + dy[k];
            if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && check(x, y))
                dfs(x, y);
        }
    }
    bool check(int i, int j) 
    {
        int tmp = 0;
        while (i) 
        {
            tmp += i % 10;
            i /= 10;
        }
        while (j) 
        {
            tmp += j % 10;
            j /= 10;
        }
        return tmp <= k;
    }
};

三、结束语 

今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

​​ 

评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值