【算法题】BFS:FloodFill

广度优先搜索(BFS)是处理二维网格(矩阵)类问题的"标配"算法,核心逻辑是从起始点出发,按"层"向四周扩散,结合"方向数组"处理上下左右四个方向的邻接节点。它能高效解决"区域填充""连通域计数/面积计算""包围区域判定"等经典网格问题,是算法面试中网格类题目最常考的解法之一。本文通过4道经典区域填充题目,拆解BFS的核心解题框架与场景化应用技巧。

一、图像渲染(洪水填充)

题目描述:

给定一个二维整数数组 image 表示图像像素值,以及起始坐标 (sr, sc) 和目标颜色 color,执行"洪水填充":将起始坐标所在的连通区域(像素值与起始点相同)全部替换为目标颜色(连通区域定义为上下左右相邻的同值像素)。

示例

  • 输入:image = [[1,1,1],[1,1,0],[1,0,1]], sr = 1, sc = 1, color = 2
  • 输出:[[2,2,2],[2,2,0],[2,0,1]]

解题思路:

标准BFS扩散框架 + 方向数组:

  1. 记录起始点的原始颜色 prev,若 prev == color 直接返回(无需填充)。
  2. 初始化队列,将起始坐标入队。
  3. 循环处理队列:
    • 取出队首坐标,将其像素值改为目标颜色;
    • 遍历四个方向,若邻接坐标在网格内且像素值为 prev,则入队。
  4. 遍历结束后返回修改后的图像。

完整代码:

cpp 复制代码
class Solution {
    int dx[4] = {0, 0, 1, -1}; // 上下左右方向数组(行偏移)
    int dy[4] = {1, -1, 0, 0}; // 列偏移
public:
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {
        int prev = image[sr][sc];
        if(prev == color) return image; // 无需填充
        queue<pair<int, int>> q;
        q.push({sr, sc});

        while(!q.empty())
        {
            auto [a, b] = q.front();
            q.pop();
            image[a][b] = color; // 填充当前点
            // 遍历四个方向
            for(int i = 0; i < 4; i++)
            {
                int x = a + dx[i], y = b + dy[i];
                // 边界检查 + 颜色匹配
                if(x >= 0 && x < image.size() && y >= 0 && y < image[0].size() && image[x][y] == prev)
                    q.push({x, y});
            }
        }
        return image;
    }
};

复杂度分析:

  • 时间复杂度:O(mn)O(mn)O(mn),m/n 为网格的行/列数,每个像素最多入队一次。
  • 空间复杂度:O(mn)O(mn)O(mn),队列最坏情况存储所有需要填充的像素(如全图同色)。

二、岛屿数量

题目描述:

给定一个由 '1'(陆地)和 '0'(水)组成的二维网格,计算岛屿的数量(岛屿由相邻的陆地连接而成,相邻指上下左右方向,网格边界外视为水)。

示例

  • 输入:

    复制代码
    [["1","1","0","0","0"],
     ["1","1","0","0","0"],
     ["0","0","1","0","0"],
     ["0","0","0","1","1"]]
  • 输出:3

解题思路:

BFS遍历 + 访问标记:

  1. 初始化访问标记数组 vis,记录已遍历的陆地坐标。
  2. 遍历网格的每个坐标:
    • 若当前坐标是陆地且未访问,岛屿数 ret++,启动BFS遍历该岛屿的所有连通陆地;
  3. BFS逻辑:
    • 起始坐标入队并标记为已访问;
    • 遍历四个方向,若邻接坐标是陆地且未访问,入队并标记。

完整代码:

cpp 复制代码
class Solution {
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    bool vis[301][301] = {false}; // 访问标记
    int m, n; // 网格行数、列数
public:
    int numIslands(vector<vector<char>>& 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(grid[i][j] == '1' && !vis[i][j])
                {
                    ret++;
                    bfs(grid, i, j); // 遍历当前岛屿的所有连通陆地
                }
            }

        return ret;
    }

    void bfs(vector<vector<char>>& grid, int i, int j)
    {
        queue<pair<int, int>> q;
        q.push({i, j});
        vis[i][j] = true; // 标记已访问

        while(!q.empty())
        {
            auto [a, b] = q.front();
            q.pop();
            // 遍历四个方向
            for(int k = 0; k < 4; k++)
            {
                int x = a + dx[k], y = b + dy[k];
                // 边界检查 + 陆地 + 未访问
                if(x >= 0 && y >= 0 && x < grid.size() && y < grid[0].size() && grid[x][y] == '1' && !vis[x][y])
                {
                    vis[x][y] = true;
                    q.push({x, y});
                }
            }
        }
    }
};

复杂度分析:

  • 时间复杂度:O(mn)O(mn)O(mn),每个网格点仅遍历一次。
  • 空间复杂度:O(mn)O(mn)O(mn),访问标记数组 + 队列(最坏情况存储所有陆地坐标)。

三、岛屿的最大面积

题目描述:

给定一个由 0(水)和 1(陆地)组成的二维网格,返回最大的岛屿面积(岛屿定义同"岛屿数量")。

示例

  • 输入:

    复制代码
    [[0,0,1,0,0,0,0,1,0,0,0,0,0],
     [0,0,0,0,0,0,0,1,1,1,0,0,0],
     [0,1,1,0,1,0,0,0,0,0,0,0,0],
     [0,1,0,0,1,1,0,0,1,0,1,0,0],
     [0,1,0,0,1,1,0,0,1,1,1,0,0],
     [0,0,0,0,0,0,0,0,0,0,1,0,0],
     [0,0,0,0,0,0,0,1,1,1,0,0,0],
     [0,0,0,0,0,0,0,1,1,0,0,0,0]]
  • 输出:6

解题思路:

BFS遍历 + 面积计数:

  1. 初始化访问标记数组,遍历每个网格点;
  2. 若当前点是陆地且未访问,启动BFS并统计该岛屿的面积;
  3. BFS逻辑:
    • 起始坐标入队、标记已访问,初始化面积计数器 count = 1
    • 遍历四个方向,每找到一个未访问的陆地,计数器+1并入队标记;
  4. 维护全局最大面积 ret,遍历结束后返回。

完整代码:

cpp 复制代码
class Solution {
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int m, n;
    bool vis[51][51] = {false}; // 访问标记
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(grid[i][j] == 1 && !vis[i][j])
                {
                    // 更新最大面积
                    ret = max(ret, bfs(grid, i, j));
                }
            }
        return ret;
    }

    int bfs(vector<vector<int>>& grid, int i, int j)
    {
        queue<pair<int, int>> q;
        q.push({i, j});
        vis[i][j] = true;
        int count = 1; // 初始面积为1(当前点)

        while(!q.empty())
        {
            auto [a, b] = q.front();
            q.pop();
            // 遍历四个方向
            for(int i = 0; i < 4; i++)
            {
                int x = a + dx[i], y = b + dy[i];
                // 边界检查 + 陆地 + 未访问
                if(x >= 0 && y >= 0 && x < grid.size() && y < grid[0].size() && grid[x][y] == 1 && !vis[x][y])
                {
                    vis[x][y] = true;
                    q.push({x, y});
                    count++; // 面积+1
                }
            }
        }
        return count;
    }
};

复杂度分析:

  • 时间复杂度:O(mn)O(mn)O(mn),每个网格点仅遍历一次。
  • 空间复杂度:O(mn)O(mn)O(mn),访问标记数组 + 队列(最坏情况存储最大岛屿的所有陆地)。

四、被围绕的区域

题目描述:

给定一个由 'X''O' 组成的二维网格,将所有被 'X' 围绕的 'O' 替换为 'X'(边界上的 'O' 及其连通的 'O' 不会被围绕,需保留)。

示例

  • 输入:

    复制代码
    [["X","X","X","X"],
     ["X","O","O","X"],
     ["X","X","O","X"],
     ["X","O","X","X"]]
  • 输出:

    复制代码
    [["X","X","X","X"],
     ["X","X","X","X"],
     ["X","X","X","X"],
     ["X","O","X","X"]]

解题思路:

反向思维 + BFS标记:

  1. 核心逻辑:仅保留边界连通的'O',其余'O'替换为'X'
  2. 遍历网格的所有边界坐标:
    • 若边界坐标是 'O',启动BFS,将其及连通的 'O' 标记为临时字符(如 '.');
  3. 遍历整个网格:
    • 将未标记的 'O' 替换为 'X'
    • 将标记的 '.' 还原为 'O'

完整代码:

cpp 复制代码
class Solution {
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int m, n;
public:
    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') bfs(board, i, 0);
            if(board[i][n - 1] == 'O') bfs(board, i, n - 1); // 修正原代码笔误:n-1而非m-1
        }
        // 遍历边界:上下行
        for(int j = 0; j < n; j++)
        {
            if(board[0][j] == 'O') bfs(board, 0, j);
            if(board[m - 1][j] == 'O') bfs(board, m - 1, j);
        }
            
        // 替换字符
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
            {
                if(board[i][j] == 'O') board[i][j] = 'X'; // 被围绕的O替换为X
                if(board[i][j] == '.') board[i][j] = 'O'; // 边界连通的O还原
            }
    }

    void bfs(vector<vector<char>>& board, int i, int j)
    {
        queue<pair<int, int>> q;
        q.push({i, j});
        board[i][j] = '.'; // 标记为临时字符

        while(!q.empty())
        {
            auto [a, b] = q.front();
            q.pop();
            
            // 遍历四个方向
            for(int i = 0; i < 4; i++)
            {
                int x = a + dx[i], y = b + dy[i];
                // 边界检查 + 未标记的O
                if(x >= 0 && y >= 0 && x < board.size() && y < board[0].size() && board[x][y] == 'O')
                {
                    board[x][y] = '.';
                    q.push({x, y});
                }
            }
        }
    }
};

注:原代码中存在笔误 board[i][n - 1] == 'O') bfs(board, i, m - 1),已修正为 n - 1,否则会导致列坐标越界。

复杂度分析:

  • 时间复杂度:O(mn)O(mn)O(mn),每个网格点仅遍历两次(边界BFS + 全局替换)。
  • 空间复杂度:O(mn)O(mn)O(mn),队列最坏情况存储所有边界连通的'O'。

二维网格BFS通用框架总结

所有网格类BFS问题均可复用以下核心框架:

cpp 复制代码
// 1. 定义方向向量(上下左右)
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};

// 2. BFS核心函数
void bfs(网格类型& grid, 起始坐标i, 起始坐标j) {
    queue<pair<int, int>> q;
    q.push({i, j});
    标记起始点(访问/临时字符/颜色);

    while(!q.empty()) {
        auto [a, b] = q.front();
        q.pop();

        // 遍历四个方向
        for(int k = 0; k < 4; k++) {
            int x = a + dx[k], y = b + dy[k];
            // 3. 边界检查 + 有效节点判断(未访问/目标颜色/陆地等)
            if(x >= 0 && y >= 0 && x < 网格行数 && y < 网格列数 && 节点有效条件) {
                标记节点;
                q.push({x, y});
            }
        }
    }
}
相关推荐
long3162 小时前
弗洛伊德·沃肖算法 Floyd Warshall Algorithm
java·后端·算法·spring·springboot·图论
有一个好名字2 小时前
力扣-咒语和药水的成功对数
java·算法·leetcode
Loo国昌2 小时前
【LangChain1.0】第一篇:基础认知
后端·python·算法·语言模型·prompt
H Corey2 小时前
Java--面向对象之继承与多态
java·开发语言·windows·学习·算法·intellij-idea
永远都不秃头的程序员(互关)2 小时前
【K-Means深度探索(三)】告别“初始陷阱”:K-Means++优化质心初始化全解析!
算法·机器学习·kmeans
程序员-King.2 小时前
day136—快慢指针—重排链表(LeetCode-143)
算法·leetcode·链表·快慢指针
万行2 小时前
差速两轮机器人位移与航向角增量计算
人工智能·python·算法·机器人
qq_336313932 小时前
java基础-多线程练习
java·开发语言·算法
不知名XL2 小时前
day25 贪心算法 part03
算法·贪心算法