FloodFill算法:图像处理与游戏开发利器

简介

本质和之前做过的黄金矿工有点类似

也就是下一场洪水,洪水把负数即低谷的地方给淹没了,即找到淹没的地方

也就是找到性质相同的一个连通块,即负数的一块区域

floodfill算法可以使用dfs,也可以使用bfs,无非就是dfs就是一条路走到黑,走到不能再走再回溯,bfs即一层一层的遍历,即加入队列当中,本章节重点讲解dfs

所以你可以发现和我们的黄金矿工解题有点类似,就是遍历整个数组,如果是负数,就进入dfs,然后上下左右看能不能走

本质是性质相同的连通块的解
Flood Fill(洪水填充)是一种经典的连通区域染色算法 ,核心思想就像画图软件里的「油漆桶工具」:从一个起点出发,把所有和起点 "连通" 且颜色相同的格子,统一染成新颜色。它本质是深度优先搜索(DFS)或广度优先搜索(BFS)的典型应用,专门用于处理连通区域的批量修改

  • 画图软件:油漆桶工具(填充相同颜色的封闭区域)。
  • 图像处理:图像分割(提取连通的物体区域)、去除噪声(填充小的孤立点)。
  • 游戏开发
    • 消消乐类游戏:消除连通的相同方块。
    • 扫雷游戏:点击空白区域时,自动展开所有连通的空白格子。
    • 地图探索:标记已探索的连通区域。
  • 算法题:连通分量计数、岛屿数量、最大面积岛屿等问题,本质都是 Flood Fill 的变种。

图像渲染

733. 图像渲染 - 力扣(LeetCode)

这道题的处理就跟我们之前的矩阵处理一样,我们之前是遍历每个点,但是本题给出了初始点,所以更加简单,初始点上下左右去递归深搜即可

注意:本题的博主再编写时出现了死递归,因为如果当初始点的颜色和color的颜色一样的时候就出现死递归,所以一开始需要if判断一下

遇到初始目标值颜色与新颜色相同的时候,会出现反复遍历相同目标的情况,所以在遇到相同颜色值的时候直接返回原图像就好了。

cpp 复制代码
class Solution {
public:
    int m, n;
    int sour;
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc,int color) {
        m = image.size();
        n = image[0].size();
        sour = image[sr][sc];
        if(sour==color){
            return image;
        }
        dfs(image, sr, sc, color);
        return image;
    }
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    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 = dx[k] + i;
            int y = dy[k] + j;
            if (x >= 0 && x < m && y >= 0 && y < n && image[x][y] == sour) {
                dfs(image, x, y, color);
            }
        }
    }
};

岛屿数量

200. 岛屿数量 - 力扣(LeetCode)

本质都是一样的,这里你可以使用check数组,也可以把原来grid中的1改为0

那如何统计岛屿的数量呢?一次最外层dfs结束之后进行统计,dfs深搜进去改为0,那所有连通的区域都变为0了,这样ret++,然后下一次寻找起点1,然后再dfs,dfs进去之后出来ret++

cpp 复制代码
class Solution {
public:
    int ret;
    int m, n;
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
    int numIslands(vector<vector<char>>& 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] - '0' == 1) {
                    dfs(grid, i, j);
                    ret++;
                }
            }
        }
        return ret;
    }
    void dfs(vector<vector<char>>& grid, int i, int j) {
        grid[i][j] = '0';
        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] - '0' == 1) {
                dfs(grid, x, y);
            }
        }
    }
};

岛屿的最大面积

695. 岛屿的最大面积 - 力扣(LeetCode)

本题和前面一样,无非就是结果如何保存,这道题就是统计一个连通块中的1的数量,可以使用一个全局变量统计,然后每次进入岛屿的时候,把全局变量重新计算即可

cpp 复制代码
class Solution {
public:
    int path;
    int ret;
    int m, n;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    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) {
                    path=1;
                    dfs(grid, i, j);
                }
            }
        }
        return ret;
    }
    void dfs(vector<vector<int>>& grid, int i, int j) {
        grid[i][j]=0;
        if (path > ret) {
            ret = path;
        }
        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) {
                path++;
                dfs(grid, x, y);
            }
        }
    }
};

被围绕的区域

130. 被围绕的区域 - 力扣(LeetCode)

本题不能想,用一个check数组,把合法的位置标注为true,然后dfs进去判断,如果不是边界那肯定会被围绕,说明合法,出来之后,把true的位置改为'X'即可,如果dfs的过程当中出现了边缘的'O',那说明不能被围绕,dfs向上传递信号false,说明这次的深搜不合法

但是博主编写的时候就发现了超时,四次for循环

cpp 复制代码
class Solution {
public:
    int m, n;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    bool check[200][200];
    void solve(vector<vector<char>>& board) {
        m = board.size();
        n = board[0].size();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (board[i][j] == 'O') {
                    check[i][j]=true;
                    if(dfs(board, i, j)){
                        for(int i=0;i<m;i++){
                            for(int j=0;j<n;j++){
                                if(check[i][j]){
                                    board[i][j]='X';
                                }
                            }
                        }
                    }
                    check[i][j]=false;
                }
            }
        }
    }
    bool dfs(vector<vector<char>>& board, int i, int j) {
        if(i==0||i==m-1||j==0||j==n-1){
            return false;
        }
        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 &&!check[x][y]&& board[x][y] == 'O') {
                check[x][y]=true;
                if(dfs(board, x, y)==false){
                    check[x][y]=false;
                    return false;
                }
                check[x][y]=false;
            }
        }
        return true;
    }
};

正难则反:我们先去处理边界情况,把边界的进行深度优先遍历

第一种用bool数组标记为已经使用过即可

第二种修改成特殊字符,然后在对非边界进行深度优先修改成X,最后把特殊字符改为O即可

cpp 复制代码
class Solution {
public:
    int m, n;
    bool vis[200][200];
    void solve(vector<vector<char>>& board) {
        m = board.size();
        n = board[0].size();
        for (int i = 0; i < m; i++) {
            if (board[i][0] == 'O' && !vis[i][0]) {
                dfs(board, i, 0);
            }
        }
        for (int j = 0; j < n; j++) {
            if (board[0][j] == 'O' && !vis[0][j]) {
                dfs(board, 0, j);
            }
        }
        for (int i = 0; i < m; i++) {
            if (board[i][n - 1] == 'O' && !vis[i][n - 1]) {
                dfs(board, i, n - 1);
            }
        }
        for (int j = 0; j < n; j++) {
            if (board[m - 1][j] == 'O' && !vis[m - 1][j]) {
                dfs(board, m - 1, j);
            }
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (!vis[i][j] && board[i][j] == 'O') {
                    board[i][j] = 'X';
                }
            }
        }
    }
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    void dfs(vector<vector<char>>& board, int i, int j) {
        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 < m && y >= 0 && y < n && board[x][y] == 'O' &&
                !vis[x][y]) {

                dfs(board, x, y);
            }
        }
    }
};

太平洋大西洋水流问题

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

本题和上题一样

解法一:暴力枚举每一个点,看是否可以同时流向大西洋和太平洋,但这个会重复走很多路导致超时

解法二:正难则反,看太平洋和大西洋的水可以分别流到哪里,最后取交集即可

cpp 复制代码
class Solution {
public:
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int m, n;
    bool pacific[200][200];
    bool atlantic[200][200];
    vector<vector<int>> ret;
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        m = heights.size();
        n = heights[0].size();
        for (int i = 0; i < m; i++) {
            if (!pacific[i][0]) {
                pacific[i][0] = true;
                dfs1(heights, i, 0);
            }
        }
        for (int j = 0; j < n; j++) {
            if (!pacific[0][j]) {
                pacific[0][j] = true;
                dfs1(heights, 0, j);
            }
        }
        for (int i = 0; i < m; i++) {
            if (!atlantic[i][n - 1]) {
                atlantic[i][n - 1] = true;
                dfs2(heights, i, n - 1);
            }
        }
        for (int j = 0; j < n; j++) {
            if (!atlantic[m - 1][j]) {
                atlantic[m - 1][j] = true;
                dfs2(heights, m - 1, j);
            }
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (atlantic[i][j] && pacific[i][j]) {
                    ret.push_back({i, j});
                }
            }
        }
        return ret;
    }
    void dfs1(vector<vector<int>>& heights, 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 &&
                heights[x][y] >= heights[i][j] && !pacific[x][y]) {
                pacific[x][y] = true;
                dfs1(heights, x, y);
            }
        }
    }
    void dfs2(vector<vector<int>>& heights, 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 &&
                heights[x][y] >= heights[i][j] && !atlantic[x][y]) {
                atlantic[x][y] = true;
                dfs2(heights, x, y);
            }
        }
    }
};

优化点就是不要把数组设为全局,传参进去即可,这样dfs可以只写一份,否则设为全局需要写两份

扫雷游戏

529. 扫雷游戏 - 力扣(LeetCode)

重在理解题意:

本质就是先判断一个点开的区域周围有多少颗雷

如果是0,那就变为B,然后递归展开8个方向

如果是多颗雷,直接改为数字返回即可

如果点到的位置是雷,直接修改然后返回即可

cpp 复制代码
class Solution {
public:
    int dx[8] = {0, 0, 1, 1, 1, -1, -1, -1};
    int dy[8] = {-1, 1, -1, 1, 0, -1, 1, 0};
    int m,n;
    vector<vector<char>> updateBoard(vector<vector<char>>& board,vector<int>& click) {
        m=board.size();
        n=board[0].size();
        dfs(board, click[0], click[1]);
        return board;
    }
    void dfs(vector<vector<char>>& board, int i, int j) {
        if(board[i][j]=='M'){
            board[i][j]='X';
            return ;
        }
        int count = 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) {
                if (board[x][y] == 'M') {
                    count++;
                }
            }
        }
        if (count == 0) {
            // 说明四周没有地雷
            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') {
                    dfs(board,x,y);
                }
            }
        } else {
            board[i][j] = count +'0';
        }
    }
};

机器人的移动范围

LCR 130. 衣橱整理 - 力扣(LeetCode)

本题相比上面的简直简单暴了,可以自行做

cpp 复制代码
class Solution {
public:
    int ret;
    int m,n;
    int digit(int x){
        int sum=0;
        while(x){
            sum+=x%10;
            x=x/10;
        }
        return sum;
    }
    int dx[2]={0,1};
    int dy[2]={1,0};
    bool vis[100][100];
    int wardrobeFinishing(int _m, int _n, int cnt) {
        m=_m;
        n=_n;
        dfs(0,0,cnt);
        return ret;
    }
    void dfs(int i,int j,int cnt){
        if(digit(i)+digit(j)<=cnt){
            vis[i][j]=true;
            ret++;
            for(int k=0;k<2;k++){
                int x=dx[k]+i;
                int y=dy[k]+j;
                if(x>=0&&x<m&&y>=0&&y<n&&!vis[x][y]){
                    dfs(x,y,cnt);
                }
            }
        }
    }

};
相关推荐
山峰哥1 小时前
SQL索引优化实战:3000字深度解析查询提速密码
大数据·数据库·sql·编辑器·深度优先
wWYy.2 小时前
算法:四数相加||
算法
新能源BMS佬大2 小时前
【仿真到实战】STM32落地EKF算法实现锂电池SOC高精度估算(含硬件驱动与源码)
stm32·嵌入式硬件·算法·电池soc估计·bms电池管理系统·扩展卡尔曼滤波估计soc·野火开发板
wen__xvn2 小时前
模拟题刷题2
算法
AI 菌2 小时前
DeepSeek-OCR 解读
人工智能·算法·计算机视觉·大模型·ocr
历程里程碑2 小时前
Linux 5 目录权限与粘滞位详解
linux·运维·服务器·数据结构·python·算法·tornado
yi.Ist2 小时前
关于若干基础的几何问题
c++·学习·算法·计算几何
毅炼2 小时前
Netty 常见问题总结
java·网络·数据结构·算法·哈希算法
Anastasiozzzz3 小时前
leetcodehot100--最小栈 MinStack
java·javascript·算法