【每日算法】专题十五_BFS 解决 FloodFill 算法

1. 算法思想

Flood Fill 问题的核心需求

给定一个二维网格(如像素矩阵)、一个起始坐标 (x, y) 和目标颜色 newColor,要求:

  1. 将起始点 (x, y) 的颜色替换为 newColor
  2. 递归地将所有与起始点相邻(上下左右) 且颜色与起始点原始颜色相同的区域,也替换为 newColor

BFS 解决 Flood Fill 的算法思想

BFS 通过队列实现 “逐层扩散”,步骤如下:

1. 记录原始颜色,处理边界情况
  • 首先获取起始点 (x, y) 的原始颜色 oldColor
  • 若 oldColor 与 newColor 相同,直接返回(无需填充,避免死循环)。
2. 初始化队列,标记起始点
  • 将起始点 (x, y) 加入队列,作为 BFS 的起点。
  • 立即将起始点的颜色更新为 newColor(或通过访问标记记录已处理,避免重复处理)。
3. 逐层扩散,填充相邻区域
  • 循环取出队列中的节点 (x, y),检查其上下左右四个相邻节点
    • 若相邻节点在网格范围内(未越界)。
    • 且相邻节点的颜色为 oldColor(与起始点原始颜色相同)。
  • 则将该相邻节点加入队列,并立即更新其颜色为 newColor(或标记为已处理)。
4. 队列清空,完成填充
  • 当队列中所有节点都处理完毕后,所有与起始点连通的 oldColor 区域已被替换为 newColor,算法结束。

示例:图像着色(Flood Fill 经典场景)

假设有如下像素网格(oldColor=1newColor=2,起始点 (1, 1)):

[[1, 1, 0],[1, 1, 0],[0, 0, 1]
]

BFS 执行过程:

  1. 起始点 (1,1) 入队,颜色更新为 2,队列:[(1,1)]
  2. 取出 (1,1),检查四邻:
    • (0,1) 是 1 → 入队,更新为 2;队列:[(0,1)]
    • (2,1) 是 0 → 跳过。
    • (1,0) 是 1 → 入队,更新为 2;队列:[(0,1), (1,0)]
    • (1,2) 是 0 → 跳过。
  3. 取出 (0,1),检查四邻:
    • (0,0) 是 1 → 入队,更新为 2;队列:[(1,0), (0,0)]
    • 其他邻点已处理或颜色不符,跳过。
  4. 取出 (1,0) 和 (0,0),检查其邻点,均无未处理的 1,队列清空。
  5. 最终结果(所有连通的 1 均变为 2):

[[2, 2, 0],[2, 2, 0],[0, 0, 1]
]

关键逻辑解析

  • 为什么用 BFS?:BFS 按 “距离起始点由近及远” 的顺序填充,适合需要 “逐层扩散” 的场景,且能保证所有连通区域被完整覆盖。
  • 避免重复处理:通过 “立即更新颜色为 newColor” 替代单独的访问标记数组,节省空间(因为 oldColor != newColor,已处理节点不会被再次加入队列)。
  • 边界检查:每次访问邻点前需判断 x 和 y 是否在网格范围内(0 ≤ x < 行数0 ≤ y < 列数)。

算法复杂度

  • 时间复杂度O(n*m),其中 n 为网格行数,m 为列数。每个单元格最多被访问一次。
  • 空间复杂度O(n*m),最坏情况下队列需存储所有单元格(如整个网格都是 oldColor 时)。

总结

BFS 解决 Flood Fill 的核心是用队列管理待处理节点,逐层扩散并实时更新颜色,确保所有与起始点连通的相同颜色区域被高效、完整地填充。该思路直观且易于实现,是处理连通区域填充问题的首选方法之一。

2. 例题

2.1 图像渲染

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

核心思路

  1. 颜色检查与预处理

    • 获取起始点 (sr, sc) 的原始颜色 prev
    • 若 prev 与目标颜色 color 相同,直接返回原图(避免重复填充)。
  2. BFS 初始化

    • 将起始点 (sr, sc) 加入队列 q
    • 获取图像的行数 n 和列数 m,用于边界检查。
  3. 逐层扩散填充

    • 循环处理队列中的每个点,将其颜色替换为 color
    • 检查该点的上下左右四个相邻点
      • 若相邻点在图像范围内且颜色等于 prev,将其加入队列。
    • 队列处理完毕后,所有连通的 prev 颜色区域均被替换为 color

关键逻辑解析

  1. 为什么用 BFS?
    BFS 按 “距离起始点由近及远” 的顺序处理节点,确保所有连通区域被完整覆盖,且避免重复访问。

  2. 如何避免重复处理?
    当一个点被加入队列时,立即将其颜色更新为 color。后续检查相邻点时,由于 image[x][y] == prev 的条件,已处理的点(颜色已变为 color)不会被重复加入队列。

  3. 边界检查的重要性
    x >= 0 && x < n && y >= 0 && y < m 确保不会越界访问图像。

示例演示

原图(prev=1color=2,起始点 (1, 1)):

[[1, 1, 0],[1, 1, 0],[0, 0, 1]
]

BFS 执行过程:

  1. 起始点 (1,1) 入队,颜色更新为 2,队列:[(1,1)]
  2. 处理 (1,1),检查四邻:
    • (0,1) 颜色为 1 → 入队,更新为 2
    • (1,0) 颜色为 1 → 入队,更新为 2
    • 其他邻点颜色为 0 或越界,跳过。
  3. 处理 (0,1),检查四邻:
    • (0,0) 颜色为 1 → 入队,更新为 2
  4. 处理 (1,0) 和 (0,0),无符合条件的邻点。
  5. 队列为空,处理结束,结果:

[[2, 2, 0],[2, 2, 0],[0, 0, 1]
]

复杂度分析

  • 时间复杂度O(n*m),其中 n 和 m 分别为图像的行数和列数。每个像素最多被访问一次。
  • 空间复杂度O(n*m),最坏情况下队列可能存储所有像素(如整个图像颜色相同)。

总结

该算法通过 BFS 高效地实现了 Flood Fill,核心在于利用队列逐层扩散实时更新颜色以避免重复处理。这种方法简洁直观,适用于处理图像连通区域的填充问题。

 

class Solution {// 表示x和y坐标typedef pair<int, int> PII;// 上下左右四个方向的偏移量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<PII> q;q.push({sr, sc});int n = image.size(), m = image[0].size();while(q.size()){auto [x1, y1] = q.front();q.pop();image[x1][y1] = color; for(int i = 0; i < 4; ++i){int x = x1 + dx[i], y = y1 + dy[i];// 找到四个方向符合条件的位置if(x >= 0 && x < n && y >= 0 && y < m && image[x][y] == prev){q.push({x, y}); }}}return image;}
};

 

2.2 岛屿数量

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

 

核心思路

  1. 遍历网格,寻找未访问的陆地
    遍历二维网格的每个单元格,当遇到值为 '1'(表示陆地)且未被访问过(!vis[i][j])的单元格时,说明发现了一个新的岛屿。

  2. BFS 扩散标记整个岛屿
    对每个新发现的陆地单元格,启动 BFS:

    • 将该单元格加入队列,作为 BFS 的起点。
    • 从队列中取出单元格,检查其上下左右四个相邻单元格
      • 若相邻单元格在网格范围内(未越界)、值为 '1' 且未被访问过,则将其加入队列,并标记为已访问(vis[x][y] = true)。
    • 此过程会递归遍历完当前岛屿的所有相连陆地,确保整个岛屿被完整标记。
  3. 计数岛屿数量
    每启动一次 BFS,代表发现并处理了一个完整的岛屿,因此计数器(ret)加 1。最终计数器的值即为网格中岛屿的总数量。

关键逻辑解析

  • vis 数组的作用:记录已访问的陆地单元格,避免重复统计同一岛屿的单元格。
  • BFS 的优势:通过队列实现 “逐层扩散”,确保所有与起始点连通的陆地都被标记,高效覆盖整个岛屿。
  • 边界检查:通过 x >= 0 && x < n && y >= 0 && y < m 确保不访问网格外的无效区域。

总结

算法通过遍历网格发现新岛屿,利用 BFS 标记整个岛屿的所有陆地,最终统计岛屿数量。核心是用 BFS 实现连通区域的完整覆盖用访问标记避免重复统计,时间复杂度为 O(n*m)nm 为网格行列数),每个单元格最多被访问一次。

class Solution {typedef pair<int, int> PII;int dx[4] = {0, 0, 1, -1};int dy[4] = {1, -1, 0, 0};int n, m;bool vis[300][300];public:int numIslands(vector<vector<char>>& grid) {int ret = 0;n = grid.size(), m = grid[0].size();for(int i = 0; i < n; ++i){for(int j = 0; j < m; ++j){if(grid[i][j] == '1' && !vis[i][j]){++ret;dfs(grid, i, j);}}}return ret;}void dfs(vector<vector<char>>& grid, int i, int j){queue<PII> q;q.push({i, j});while(q.size()){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 && x < n && y >= 0 && y < m && grid[x][y] == '1' && !vis[x][y]){q.push({x, y});vis[x][y] = true; }}}}
};

 

2.3 岛屿的最大面积

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

核心思路

  1. 遍历网格,寻找未访问的陆地
    遍历二维网格的每个单元格,当遇到值为 1(表示陆地)且未被访问过(!vis[i][j])的单元格时,说明发现了一个新的岛屿。

  2. BFS 计算当前岛屿面积
    对每个新发现的陆地单元格,启动 BFS 计算整个岛屿的面积:

    • 将该单元格加入队列,标记为已访问(vis[i][j] = true),初始化面积计数器(count = 1)。
    • 从队列中取出单元格,检查其上下左右四个相邻单元格
      • 若相邻单元格在网格范围内、值为 1 且未被访问过,则将其加入队列,标记为已访问,并将面积计数器加 1。
    • 队列处理完毕后,count 即为当前岛屿的面积。
  3. 更新最大岛屿面积
    每次计算完一个岛屿的面积后,用当前面积更新全局最大面积(ret = max(ret, count))。遍历结束后,ret 即为网格中最大岛屿的面积。

关键逻辑解析

  • vis 数组的作用:记录已访问的陆地单元格,避免重复计算同一岛屿的面积。
  • BFS 的优势:通过队列实现 “逐层扩散”,完整覆盖当前岛屿的所有陆地,确保面积计算准确。
  • 面积统计:从起始单元格开始,每纳入一个新的陆地单元格,面积计数器就加 1,最终得到整个岛屿的面积。

总结

算法通过遍历网格发现新岛屿,利用 BFS 计算每个岛屿的面积,实时更新最大面积。核心是用 BFS 完整覆盖连通区域以计算面积用访问标记避免重复统计,时间复杂度为 O(n*m)nm 为网格行列数),每个单元格最多被访问一次。

 

class Solution {typedef pair<int, int> PII;int dx[4] = {0, 0, 1, -1};int dy[4] = {1, -1, 0, 0};int vis[50][50];int n, m;int ret = 0;public:int maxAreaOfIsland(vector<vector<int>>& grid) {n = grid.size(), m = grid[0].size();for(int i = 0; i < n; ++i){for(int j = 0; j < m; ++j){if(grid[i][j] == 1 && !vis[i][j])dfs(grid, i, j);}}return ret;}void dfs(vector<vector<int>>& grid, int i, int j){queue<PII> q;q.push({i, j});vis[i][j] = true;int count = 1;while(q.size()){auto [a, b] = q.front();q.pop();for(int k = 0; k < 4; ++k){int x1 = a + dx[k], y1 = b + dy[k];if(x1 < n && x1 >= 0 && y1 < m && y1 >= 0 && grid[x1][y1] == 1 && !vis[x1][y1]){++count;q.push({x1, y1});vis[x1][y1] = true;}}}ret = max(ret, count);}
};

 

2.4 被围绕的区域

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

核心思想

  1. 遍历网格,定位未访问的 'O'
    遍历整个网格,当遇到值为 'O' 且未被访问(!vis[i][j])的单元格时,启动 BFS 处理该连通区域。

  2. BFS 同步完成 “区域判断” 与 “单元格记录”
    在一次 BFS 中同时实现两个目标:

    • 记录区域所有单元格:用region数组存储当前连通区域的所有 'O' 的坐标。
    • 判断区域是否被包围:通过hasEdge标记该区域是否包含边缘单元格(位于网格边界:x=0x=n-1y=0y=m-1)。
      • 若区域包含边缘单元格,hasEdge设为false(不被包围)。
      • 若区域无边缘单元格,hasEdge保持true(被完全包围)。
  3. 根据判断结果处理区域
    BFS 结束后,若hasEdgetrue(区域被包围),则遍历region数组,将所有记录的 'O' 转换为 'X';否则不处理(保留边缘连通区域)。

关键逻辑解析

  • 合并 BFS 的优势:避免两次遍历同一区域,一次 BFS 同时完成 “判断” 和 “记录”,减少冗余操作,时间复杂度优化为O(n*m)nm为网格行列数)。
  • region数组的作用:临时存储当前区域的所有单元格,便于后续批量转换,无需二次遍历寻找目标单元格。
  • hasEdge标记的作用:实时追踪区域是否接触网格边缘,决定该区域是否需要被转换为 'X'。

总结

算法通过一次 BFS 实现 “判断区域是否被包围” 和 “记录待处理单元格”,最终根据判断结果批量转换被包围区域。核心是用一次遍历同步完成多任务,既保证逻辑清晰,又提高了效率,完美解决 “被围绕的区域” 问题。

 

class Solution {typedef pair<int, int> PII;int dx[4] = {0, 0, 1, -1};int dy[4] = {1, -1, 0, 0};int n, m;int vis[200][200];public:void solve(vector<vector<char>>& board) {n = board.size(), m = board[0].size();for(int i = 0; i < n; ++i){for(int j = 0; j  < m; ++j){if(board[i][j] == 'O' && !vis[i][j]){bfs(board, i, j);                  }}}}void bfs(vector<vector<char>>& board, int i, int j) // 判断有没有任何单元格位于 board 边缘{bool hasEdge = true;if(i == 0 || i == n - 1 || j == 0 || j == m - 1){hasEdge = false;}vector<PII> region;queue<PII> q;q.push({i, j});region.push_back({i, j});vis[i][j] = true;while(q.size()){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 && x < n && y >= 0 && y < m && board[x][y] == 'O' && !vis[x][y]){vis[x][y] = true;if(x == 0 || x == n - 1 || y == 0 || y == m - 1){hasEdge = false;}q.push({x, y});region.push_back({x, y});}}}if(hasEdge){for(auto [x1, y1] : region)board[x1][y1] = 'X';}}
};

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/915136.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/915136.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ESLint 完整功能介绍和完整使用示例演示

以下是ESLint的完整功能介绍和完整使用示例演示&#xff1a; ESLint 完整功能介绍 一、核心功能静态代码分析&#xff1a; 通过解析JavaScript/TypeScript代码为抽象语法树&#xff08;AST&#xff09;&#xff0c;识别语法错误、潜在问题&#xff08;如未定义变量、未使用变量…

解决问题七大步骤

发现问题后寻找解决方案的流程可以细化为 7个核心步骤&#xff0c;每个步骤包含具体措施、信息源和关键技巧&#xff0c;形成“从自查到验证、从独立解决到寻求帮助”的完整闭环。以下是完善后的流程&#xff1a; 一、明确问题与初步自查&#xff08;前提&#xff1a;减少无效搜…

思维链(CoT)技术全景:原理、实现与前沿应用深度解析

一、核心概念与原理 定义与起源 CoT 是一种引导大语言模型&#xff08;LLM&#xff09;显式生成中间推理步骤的技术&#xff0c;通过模拟人类逐步解决问题的过程&#xff0c;提升复杂任务&#xff08;如数学证明、多步逻辑推理&#xff09;的准确性。该概念由 Google Brain 团…

实验-华为综合

华为综合实验 一 实验拓扑二 实验配置交换机2 vlan batch 10 20 int e0/0/2 port link-type access port default vlan 10 int e0/0/1 port link-type access port default vlan 20 int e0/0/3 port link-type trunk port trunk allow-pass vlan alltelnet交换机3 链路类型配置…

Matlab打开慢、加载慢的解决办法

安装完毕后直接打开会非常慢&#xff0c;而且打开了之后还得加载很久才能运行 解决办法如下&#xff1a; 1.找到路径“D:\Program Files\Polyspace\R2020a\licenses”&#xff08;我是把matlab安装在D盘了&#xff0c;如果是其他盘修改路径即可&#xff09;&#xff0c;该路径记…

混沌趋势指标原理及交易展示

1. 引言在金融市场交易中&#xff0c;尤其是加密货币合约交易&#xff0c;趋势跟踪是最主流的策略之一。然而&#xff0c;传统趋势指标如均线、MACD等存在明显的滞后性&#xff0c;往往在趋势确立后才发出信号&#xff0c;导致交易者错失最佳入场时机。更糟糕的是&#xff0c;市…

Java面试宝典:Maven

一、Maven的本质与核心价值 项目管理革命 POM驱动:通过pom.xml文件定义项目结构、依赖、构建规则,实现标准化管理()。示例配置: <dependencies> <dependency> <groupId>org.springframework

可靠消息最终一致性分布式事务解决方案

之前文章写过主流的一些 分布式事务的解决方案&#xff0c;但其实工作中很少有一些高并发的业务中去使用这些方案&#xff0c;因为对于高并发的场景来说&#xff0c;引入这些方案的性能损耗太大&#xff0c;且对系统事务侵入性太强影响系统稳定性。 所以在高并发的业务中&…

ISIS基础

拓扑计算方式 模型 支持的网络 支持的地址OSPF SPF TCP/IP IP网络 IPv4地址ISIS SPF OSI CLNP网络 NSAP地址集成ISIS SPF TCP/IP IP网络 NSAP地址&#xff0c;但可以支持IPv4地址12. …

基于ASP.NET+SQL Server实现(Web)排球赛事网站

排球赛事网的设计与实现摘要随着近几年来计算机技术、网络技术及相应软件技术的迅猛发展&#xff0c;人们的生活已越来越离不开计算机了&#xff0c;而且总是要花费很多时间在它上面。一直以来&#xff0c;排球作为一项大众喜爱的运动&#xff0c;得到广泛传播。随着各项排球赛…

【PTA数据结构 | C语言版】根据后序和中序遍历输出前序遍历

本专栏持续输出数据结构题目集&#xff0c;欢迎订阅。 文章目录题目代码题目 本题要求根据给定的一棵二叉树的后序遍历和中序遍历结果&#xff0c;输出该树的前序遍历结果。 输入格式: 第一行给出正整数 n (≤30)&#xff0c;是树中结点的个数。随后两行&#xff0c;每行给出…

Java HashMap高频面试题深度解析

在 Java 面试中&#xff0c;HashMap 是必问的核心知识点&#xff0c;以下是高频问题和深度解析框架&#xff0c;助你系统性掌握&#xff1a;一、基础概念HashMap 的本质是什么&#xff1f; 基于哈希表的 Map 接口实现&#xff0c;存储键值对&#xff08;Key-Value&#xff09;非…

GitHub Pages无法访问以点号.开头的目录

目录 前言 Jekyll 是什么 启用访问 总结 前言 一些前端项目经常会使用GitHub Pages进行部署展示&#xff0c;但是GitHub Pages 使用的是 Jekyll 引擎&#xff0c;对 Jekyll 引擎不熟悉的小伙伴就会出现如文章标题所言的情况。 Jekyll 是什么 Jekyll 是 GitHub Pages 默认…

JS JSON.stringify介绍(JS序列化、JSON字符串 )(遍历输入值的所有可枚举属性,将其转换为文本表示)缓存序列化、状态管理与时间旅行、replacer

文章目录JSON.stringify 全解析1. 基本概念2. 序列化原理1. 对于原始类型&#xff0c;直接转换为对应的字符串表示2. 对于对象和数组&#xff0c;递归处理其每个属性或元素3. 应用特殊规则处理日期、函数、Symbol 等特殊类型4. 检测并防止循环引用5. 应用 replacer 函数或数组进…

SQLite / LiteDB 单文件数据库为何“清空表后仍占几 GB”?——原理解析与空间回收实战

关键词&#xff1a; SQLite、LiteDB、VACUUM、WAL、auto_vacuum、文件瘦身、数据库维护在嵌入式或桌面、IoT 网关等场景&#xff0c;很多同学都会选择单文件数据库&#xff08;SQLite、LiteDB、SQL CE…&#xff09;。 最近群里一位朋友反馈&#xff1a;“我的 test.db 已经把业…

如何加固Web服务器的安全?

Web服务器是用户和公司联系的桥梁&#xff0c;Web服务器为用户交付网页内容和提供Web应用。正因为Web服务器是面向互联网的&#xff0c;所以成为了网络的攻击经常利用的一个入口。Web 服务器是企业数字化转型的 “前沿阵地”&#xff0c;其安全性不仅关乎技术层面的稳定运行&am…

MyBatis:配置文件完成增删改查_添加

1 实现添加操作 编写接口方法:Mapper接口编写sql语句&#xff1a;sql映射文件<insert id"add">insert into tb_brand(brand_name,company_name,ordered,description,status)values(#{brandName},#{companyName},#{ordered},#{description},#{status});</ins…

SGLang 推理框架核心组件解析:请求、内存与缓存的协同工作

SGLang 推理框架核心组件解析&#xff1a;请求、内存与缓存的协同工作 在当今大语言模型&#xff08;LLM&#xff09;服务的浪潮中&#xff0c;高效的推理框架是决定服务质量与成本的关键。SGLang 作为一个高性能的 LLM 推理和部署库&#xff0c;其内部精巧的设计确保了高吞吐量…

React学习笔记——Day2打卡

1、React表单控制 1.1 受控绑定 概念&#xff1a;使用React组件的状态&#xff08;useState&#xff09;控制表单的状态 完整示例&#xff1a; function App(){/* 1. 准备一个React状态值 */ const [value, setValue] useState()return (/* 2. 通过value属性绑定状态&#x…

用例测试方法5,6:状态迁移图和因果图

状态迁移图通过描绘系统的状态及引起状态转换的事件&#xff0c;来表示系统的行为例如&#xff1a;订机票l向航空公司打电话预定机票—>此时机票信息处于“完成”状态顾客支付了机票费用后—>机票信息就变为“已支付”状态旅行当天到达机场后&#xff0c;拿到机票后—>…