
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

在图论与算法实战领域,广度优先搜索(BFS)始终是不可或缺的核心工具,其"逐层遍历、先广后深"的核心特性,使其在处理连通性、路径查找、依赖排序等问题时具备天然优势,成为面试考核与工程实践中的高频考点.不同于基础理论的浅层认知,实际应用中的BFS往往需要结合场景进行灵活变形,而FloodFill、最短路径、多源BFS及拓扑排序,正是BFS实战中最具代表性、也最易让人陷入瓶颈的四大核心场景.FloodFill算法作为BFS的经典应用,以"种子扩散"为核心,广泛应用于图像处理、游戏开发等领域,其核心在于邻域判定与边界控制的精准把握;无权图中的最短路径问题,是BFS最基础的实战应用,凭借其层级遍历的特性,能够确保首次访问目标节点时的路径为最短路径,无需额外的路径优化;多源BFS则是单源BFS的扩展,通过多个起点同时遍历,高效解决多源最短路径、范围覆盖等问题,相比暴力解法大幅提升效率,适用于疫情扩散、01矩阵等场景;而基于BFS的拓扑排序(Kahn算法),则为有向无环图(DAG)的依赖排序提供了高效解决方案,在任务调度、课程安排、编译顺序等实际场景中发挥着重要作用.本文聚焦BFS实战攻坚,摒弃单纯的理论堆砌,以"场景拆解+核心逻辑+实战导向"为核心,逐一剖析FloodFill、最短路径、多源BFS及拓扑排序的解题思路、实现技巧与边界处理方法.通过梳理四大场景的共性规律与差异化特点,帮助学习者打通BFS从理论到实战的壁垒,掌握不同场景下的算法选型与优化策略,提升解决复杂算法问题的能力,为面试攻坚与工程实践奠定坚实基础.废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!
目录
- 1.BFS思想背景以及FloodFill算法简介
- 2.图像渲染(OJ题)--BFS解决FloodFill算法
- 3.岛屿的数量(OJ题)--BFS解决FloodFill算法
- 4.岛屿的最大面积(OJ题)--BFS解决FloodFill算法
- 5.被围绕的区域(OJ题)--BFS解决FloodFill算法
- 6.迷宫中离入口最近的出口(OJ题)--BFS解决最短路径问题
- 7.最小基因变化(OJ题)--BFS解决最短路径问题
- 8.单词接龙(OJ题)--BFS解决最短路径问题
- 9.为高尔夫比赛砍树(OJ题)--BFS解决最短路径问题
- 10.01矩阵(OJ题)--多源BFS问题
- 11.飞驰的数量(OJ题)--多源BFS问题
- 12.地图中的最高点(OJ题)--多源BFS问题
- 13.地图分析(OJ题)--多源BFS问题
- 14.课程表(OJ题)--BFS解决拓扑排序
- 15.课程表||(OJ题)--BFS解决拓扑排序
- 16.火星词典(OJ题)--BFS解决拓扑排序
1.BFS思想背景以及FloodFill算法简介
BFS思想背景
- 历史起源与发展
- 早期雏形:19世纪数学家康托尔、若尔当对图结构的研究中已有BFS思想的萌芽,18世纪欧拉解决哥尼斯堡七桥问题时也运用了类似的图遍历思想.
- 正式提出:1959年,美国计算机科学家爱德华·摩尔(Edward Forrest Moore)在研究迷宫寻路问题时,于论文《The Shortest Path Through a Maze》中首次正式提出BFS算法,将迷宫抽象为图结构,用BFS求解最短路径问题
- 早期应用:1961年,C.Y. Lee在电路布线问题中大规模使用BFS,形成了著名的"Lee算法"
- 理论完善:BFS的雏形最早可追溯至1945年,康拉德·楚泽(Konrad Zuse)在其未发表的博士论文中提及了BFS在图连通分量查找中的应用,该论文于1972年才正式出版.
- 核心思想与特点
- 基本定义 :广度优先搜索(Breadth-First Search, BFS)是一种逐层递进、地毯式的图遍历算法,从起始节点开始,先访问所有直接相邻的节点(第一层),再访问这些节点的所有未访问邻居(第二层),以此类推,直到遍历完所有可达节点
- 核心数据结构 :队列(Queue),遵循"先进先出(FIFO)"原则,确保节点按访问顺序处理
- 关键特性 :
- 最短路径保证 :在无权图中,BFS能找到从起点到终点的最短路径(边数最少)
- 完备性:若目标节点存在,BFS一定能找到它
- 时间复杂度:O(V+E),其中V是节点数,E是边数
- 空间复杂度:最坏情况下为O(V),需要存储所有节点
- 思想来源
BFS的核心思想源于人类的直觉式搜索行为,如:
- 向水中投入石子,水波层层扩散的物理过程
- 迷宫中从起点出发,依次探索所有相邻通道,确保不遗漏任何路径
- 社交网络中,查找与某人相距k度的所有联系人
FloodFill算法简介
-
定义与核心思想
FloodFill(泛洪填充/洪水填充)是一种区域填充算法,核心思想源于现实生活中的"洪水泛滥"过程:从一个种子点(起始点)出发,按照特定规则(如颜色相同、数值相等)向相邻区域扩散,直至遇到边界条件(如不同颜色、障碍物等)为止.其本质是在二维网格(如图像像素阵列、棋盘、地图)中识别并标记所有与种子点连通的区域.
-
连通性定义
FloodFill算法有两种常用的连通性判断标准:
| 连通类型 | 包含方向 | 适用场景 |
|---|---|---|
| 四邻域 | 上、下、左、右 | 图像处理、简单游戏地图 |
| 八邻域 | 上、下、左、右、四个对角线 | 地理信息系统、复杂连通区域分析 |
- 实现方式
FloodFill算法可通过两种经典搜索算法实现,各有优劣:
| 实现方式 | 核心数据结构 | 特点 | 适用场景 |
|---|---|---|---|
| DFS(深度优先搜索) | 递归调用栈/显式栈 | 代码简洁,"一条路走到黑",但容易栈溢出(大规模区域) | 小规模图像,学习与教学目的 |
| BFS(广度优先搜索) | 队列 | 迭代实现,空间可控,"涟漪扩散",工程实践首选 | 大规模图像,避免栈溢出问题 |
BFS实现FloodFill的基本步骤:
-
检查种子点是否在有效范围内,目标颜色与替换颜色是否不同
-
创建队列,将种子点入队,并标记为已访问
-
当队列不为空时:
- 出队一个节点
- 将其颜色改为目标颜色
- 检查四个(或八个)相邻节点
- 若相邻节点颜色为原始颜色且未访问,入队并标记为已访问
-
队列空时,算法结束
-
应用场景
FloodFill算法广泛应用于多个领域:
- 图像处理:绘图软件中的"油漆桶工具"、图像分割、去除噪点
- 游戏开发:扫雷游戏中空白区域的自动扩展、地图区域标记、角色移动范围计算
- 地理信息系统(GIS):洪水灾害模拟、区域边界提取、地形分析
- 医学图像分析:肿瘤区域识别、器官分割、血管网络提取
- 其他领域:迷宫求解、电路板布线、连通区域统计
BFS与FloodFill的关系
- 本质联系 :FloodFill是BFS/DFS算法在二维网格上的具体应用,专注于区域填充问题
- 区别 :
- BFS是通用图遍历算法,适用于任何图结构,目标是遍历所有可达节点
- FloodFill是专用区域填充算法,主要用于二维网格,目标是识别并填充连通区域
- 实现选择 :在实际开发中,优先选择BFS迭代实现FloodFill,避免DFS递归的栈溢出风险,尤其处理大规模图像时
2.图像渲染(OJ题)--BFS解决FloodFill算法

算法思路:
可以利⽤深搜或者宽搜,遍历到与该点相连的所有像素相同的点,然后将其修改成指定的像素即可.
核心逻辑(FloodFill + BFS 本质)
- 算法思想
以起始坐标为种子 ,用 BFS 逐层向外扩散 ,把所有和起始点连通、同色的格子,全部染成目标颜色. - BFS 优势
逐层遍历,天然适合区域填充、连通域查找,是 FloodFill 最直观的实现方式. - 关键细节
- 提前判重 :
prev == color直接返回,杜绝无限循环; - 边界检查 :
x/y必须在矩阵范围内,防止数组越界崩溃; - 无重复入队:只有**未染色(颜色=prev)**的格子才会入队,保证每个格子只处理一次.
- 提前判重 :
核心代码
cpp
class Solution
{
//1.工具定义:坐标类型 + 四方向偏移量(网格BFS标配)
typedef pair<int, int> PII; //简化写法:PII 代表 (行坐标, 列坐标)
int dx[4] = {0, 0, 1, -1}; //四方向:右、左、下、上
int dy[4] = {1, -1, 0, 0}; //与dx一一对应,控制上下左右移动
public:
//核心函数:图像渲染 FloodFill
//image:二维图像矩阵 | sr/sc:起始坐标 | color:目标填充颜色
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc,int color)
{
//2.记录起始位置的原始颜色
int prev = image[sr][sc];
//如果原始颜色 = 目标颜色,直接返回!避免死循环+无意义计算
if (prev == color)
return image;
//3.获取矩阵尺寸:m行 n列
int m = image.size(), n = image[0].size();
//4.BFS核心容器:队列,存储待染色的坐标
queue<PII> q;
q.push({sr, sc}); //起始点入队(种子入队)
//5.BFS 主循环:逐层扩散填充
while (!q.empty())
{
//取出队首坐标(C++17结构化绑定,等价于 a=first, b=second)
auto [a, b] = q.front();
image[a][b] = color; //核心操作:染色
q.pop(); //出队(已处理完成)
//6.四方向遍历:探索上下左右相邻格子
for (int i = 0; i < 4; i++)
{
//计算相邻格子的新坐标
int x = a + dx[i], y = b + dy[i];
//7.合法性判断(网格BFS必写)
//条件:不越界 + 颜色和原始颜色一致(未被染色)
if (x >= 0 && x < m && y >= 0 && y < n && image[x][y] == prev)
{
q.push({x, y}); //符合条件,加入队列等待处理
}
}
}
return image; //返回渲染完成的图像
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class Solution
{
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;
int m = image.size(), n = image[0].size();
queue<PII> q;
q.push({sr, sc});
//BFS 核心循环
while (!q.empty())
{
auto [a, b] = q.front();
image[a][b] = color;
q.pop();
//遍历四个方向
for (int i = 0; i < 4; i++)
{
int x = a + dx[i], y = b + dy[i];
//合法性校验:不越界 + 颜色匹配
if (x >= 0 && x < m && y >= 0 && y < n && image[x][y] == prev)
{
q.push({x, y});
}
}
}
return image;
}
};
void printImage(const vector<vector<int>>& image) {
for (const auto& row : image) {
for (int val : row) {
cout << val << " ";
}
cout << endl;
}
cout << "---------------------" << endl;
}
int main() {
Solution sol;
cout << "测试用例1:标准 FloodFill 场景" << endl;
vector<vector<int>> image1 = {
{1, 1, 1},
{1, 1, 0},
{1, 0, 1}
};
cout << "原始图像:" << endl;
printImage(image1);
//起始坐标(1,1),填充颜色2
vector<vector<int>> res1 = sol.floodFill(image1, 1, 1, 2);
cout << "填充后图像:" << endl;
printImage(res1);
cout << "测试用例2:边界剪枝场景(目标颜色=原颜色)" << endl;
vector<vector<int>> image2 = {
{0, 0, 0},
{0, 0, 0}
};
cout << "原始图像:" << endl;
printImage(image2);
//起始坐标(0,0),填充颜色0(与原颜色一致)
vector<vector<int>> res2 = sol.floodFill(image2, 0, 0, 0);
cout << "填充后图像:" << endl;
printImage(res2);
return 0;
}

3.岛屿的数量(OJ题)--BFS解决FloodFill算法

算法思路:
遍历整个矩阵,每次找到⼀块陆地的时候:
说明找到⼀个岛屿,记录到最终结果 ret ⾥⾯;
并且将这个陆地相连的所有陆地,也就是这块岛屿,全部变成海洋.这样的话,我们下次遍历到这块岛屿的时候,它已经是海洋了,不会影响最终结果.其中变成海洋的操作,可以利⽤深搜和宽搜解决,其实就是 图像渲染这道题.这样,当我们遍历完全部的矩阵的时候,ret存的就是最终结果.
🔥实战核心剖析
1. 算法本质:FloodFill 连通域计数
与上一讲图像渲染 完全同源,都是BFS FloodFill 种子扩散思想:
- 图像渲染:染色填充连通区域
- 岛屿数量:标记访问 连通区域 + 统计数量 → 这就是BFS网格问题的共性规律:一套模板
2. 实战必背技巧
- 入队即标记访问
避免同一节点多次入队,彻底解决超时问题,是BFS网格题的最优实践; - 全局变量优化
m/n/vis/dx/dy定义为成员变量,省去函数反复传参,代码更简洁高效; - 访问标记数组
独立标记遍历状态,不修改原始网格数据,工程化更友好. - 边界处理(BFS网格题通用规则)
坐标必须在[0, m)和[0, n)范围内,防止数组越界;
仅处理陆地1,跳过水域0;
仅处理未访问的节点,保证每个陆地只遍历一次. - 核心逻辑流程
遍历网格每一个单元格;
找到未访问的陆地 → 判定为新岛屿,计数器+1;
启动BFS,将整个岛屿的所有陆地 标记为已访问(FloodFill);
遍历完成后,计数器即为最终岛屿数量.
核心代码
cpp
class Solution
{
//1.实战优化:全局/成员变量(BFS网格题标配)
int dx[4] = {1, -1, 0, 0}; //四方向偏移量:下、上、右、左
int dy[4] = {0, 0, 1, -1};
bool vis[301][301]; //访问标记数组:题目网格最大300x300,避免重复遍历
int m, n; //网格行数、列数:全局化,省去BFS传参
public:
int numIslands(vector<vector<char>>& grid)
{
m = grid.size(), n = grid[0].size(); //初始化网格尺寸
int ret = 0; //岛屿数量计数器
//2.遍历整个网格:寻找未被访问的陆地起点
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
//关键判定:当前是陆地 '1' + 未被访问 = 发现新岛屿!
if (grid[i][j] == '1' && !vis[i][j])
{
ret++; //岛屿数量+1
bfs(grid, i, j); //BFS FloodFill:标记整个岛屿为已访问
}
}
}
return ret; //返回最终岛屿数
}
//3.BFS核心函数:FloodFill 连通域标记(执行层)
void bfs(vector<vector<char>>& grid, int i, int j)
{
queue<pair<int, int>> q; //队列存储坐标
q.push({i, j}); //起点入队
vis[i][j] = true; //入队即标记:实战核心技巧,避免重复入队
//BFS逐层扩散:标准FloodFill流程
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];
//4.边界合法性校验(BFS网格题必写!)
//条件:不越界 + 是陆地 + 未被访问
if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == '1' && !vis[x][y])
{
q.push({x, y});
vis[x][y] = true; //入队立即标记
}
}
}
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class Solution
{
//四方向:下、上、右、左
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
bool vis[301][301]; //访问标记数组(题目最大网格300x300)
int m, n; //全局行列数,简化传参
public:
int numIslands(vector<vector<char>>& grid)
{
m = grid.size(), n = grid[0].size();
memset(vis, 0, sizeof(vis)); //初始化访问数组(修复脏数据bug)
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); //BFS标记整块陆地
}
}
}
return ret;
}
//BFS核心:FloodFill标记整块陆地为已访问
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 && x < m && y >= 0 && y < n && grid[x][y] == '1' && !vis[x][y])
{
q.push({x, y});
vis[x][y] = true;
}
}
}
}
};
void printGrid(const vector<vector<char>>& grid) {
for (const auto& row : grid) {
for (char c : row) {
cout << c << " ";
}
cout << endl;
}
cout << "---------------------" << endl;
}
int main() {
Solution sol;
cout << "【测试用例1】标准岛屿场景" << endl;
vector<vector<char>> grid1 = {
{'1','1','0','0','0'},
{'1','1','0','0','0'},
{'0','0','1','0','0'},
{'0','0','0','1','1'}
};
cout << "原始网格:" << endl;
printGrid(grid1);
cout << "岛屿数量:" << sol.numIslands(grid1) << endl << endl;
cout << "【测试用例2】全水域场景" << endl;
vector<vector<char>> grid2 = {
{'0','0','0'},
{'0','0','0'}
};
cout << "原始网格:" << endl;
printGrid(grid2);
cout << "岛屿数量:" << sol.numIslands(grid2) << endl << endl;
cout << "【测试用例3】单格陆地场景" << endl;
vector<vector<char>> grid3 = {{'1'}};
cout << "原始网格:" << endl;
printGrid(grid3);
cout << "岛屿数量:" << sol.numIslands(grid3) << endl;
return 0;
}

4.岛屿的最大面积(OJ题)--BFS解决FloodFill算法

算法思路:
遍历整个矩阵,每当遇到⼀块⼟地的时候,就⽤深搜或者宽搜将与这块⼟地相连的整个岛屿的⾯积计算出来.然后在搜索得到的所有的岛屿⾯积求⼀个最⼤值即可.在搜索过程中,为了防⽌搜到重复的⼟地:
(1)可以开⼀个同等规模的布尔数组,标记⼀下这个位置是否已经被访问过;
(2)也可以将原始矩阵的1修改成0,但是这样操作会修改原始矩阵.
🔥实战核心拆解
1. 核心思想:FloodFill 连通域面积统计
这是岛屿数量的直接进阶,同根同源于 FloodFill 种子扩散思想:
- 岛屿数量:统计连通域的个数
- 岛屿最大面积:统计每个连通域的大小 ,取最大值→ 验证了BFS网格题一套通用模板,配多场景变形的核心规律。
2.实战必背技巧
- 入队即标记访问
杜绝节点重复入队,避免超时/死循环,是BFS网格题的最优工程实践; - 全局变量简化
dx/dy/vis/m/n作为成员变量,省去反复传参,代码更简洁高效; - 面积计数逻辑
每入队一个新陆地,计数器+11精准统计连通域大小.
- 边界处理(BFS网格通用规则)
- 坐标必须在网格范围内,防止数组越界;
- 仅遍历陆地
1,跳过水域0; - 仅处理未访问的节点,保证每个格子只遍历一次.
- 执行流程
- 遍历网格所有单元格;
- 找到未访问的陆地 → 启动BFS;
- BFS扩散整个岛屿,统计面积;
- 维护全局最大值,最终返回结果.
核心代码
cpp
class Solution
{
//1.BFS网格题通用模板:四方向偏移量(右、左、下、上)
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
// 访问标记数组:题目限定网格最大50x50,防止重复遍历
bool vis[51][51];
// 网格行列数:全局化定义,简化函数传参(实战优化技巧)
int m, n;
public:
int maxAreaOfIsland(vector<vector<int>>& grid)
{
m = grid.size(), n = grid[0].size();
int ret = 0; //存储最终结果:最大岛屿面积
//2.遍历整个网格:枚举所有可能的起点
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
//核心判定:当前是陆地 + 未被访问 → 新的岛屿
if (grid[i][j] == 1 && !vis[i][j])
{
//BFS计算当前岛屿面积,更新最大值
ret = max(ret, bfs(grid, i, j));
}
}
}
return ret;
}
//3.BFS核心函数:FloodFill扩散,统计当前岛屿的面积
int bfs(vector<vector<int>& grid, int i, int j)
{
int count = 0; //计数器:记录当前岛屿的陆地数量(面积)
queue<pair<int, int>> q; //队列存储坐标,实现BFS逐层扩散
//起点入队 + 标记访问 + 面积+1
q.push({i, j});
vis[i][j] = true;
count++;
//BFS主循环:逐层遍历陆地
while (q.size())
{
auto [a, b] = q.front(); //取出队首节点
q.pop();
//4.四方向探索相邻格子
for (int k = 0; k < 4; k++)
{
int x = a + dx[k], y = b + dy[k];
//边界合法性校验(BFS网格题必写!)
//条件:不越界 + 是陆地 + 未被访问
if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == 1 &&!vis[x][y])
{
q.push({x, y});
vis[x][y] = true; //入队即标记:避免重复入队(核心优化)
count++; //发现新陆地,面积+1
}
}
}
return count; //返回当前岛屿的面积
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
class Solution
{
//四方向偏移量:右、左、下、上(BFS网格遍历标配)
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
bool vis[51][51]; //访问标记数组,题目限制网格最大50x50
int m, n; //全局存储网格行列数,简化传参
public:
int maxAreaOfIsland(vector<vector<int>>& grid)
{
m = grid.size(), n = grid[0].size();
memset(vis, 0, sizeof(vis)); //初始化访问数组,清空脏数据
int ret = 0; // 记录最终的最大面积
//遍历整个网格,寻找未被访问的陆地
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
//发现新岛屿,BFS计算面积,并更新最大值
if (grid[i][j] == 1 && !vis[i][j])
{
ret = max(ret, bfs(grid, i, j));
}
}
}
return ret;
}
//BFS核心:FloodFill扩散,统计当前岛屿的面积
int bfs(vector<vector<int>>& grid, int i, int j)
{
int count = 0; //统计当前岛屿的陆地数量(面积)
queue<pair<int, int>> q;
//起点入队、标记访问、面积+1
q.push({i, j});
vis[i][j] = true;
count++;
//BFS逐层扩散
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 < m && y >= 0 && y < n && grid[x][y] == 1 &&!vis[x][y])
{
q.push({x, y});
vis[x][y] = true;
count++; // 每找到一块陆地,面积+1
}
}
}
return count;
}
};
void printGrid(const vector<vector<int>>& grid) {
for (const auto& row : grid) {
for (int num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "---------------------" << endl;
}
int main() {
Solution sol;
cout << "【测试用例1】标准岛屿场景" << endl;
vector<vector<int>> grid1 = {
{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}
};
cout << "原始网格:" << endl;
printGrid(grid1);
cout << "最大岛屿面积:" << sol.maxAreaOfIsland(grid1) << endl << endl;
cout << "【测试用例2】全水域场景" << endl;
vector<vector<int>> grid2 = {
{0,0,0},
{0,0,0}
};
cout << "原始网格:" << endl;
printGrid(grid2);
cout << "最大岛屿面积:" << sol.maxAreaOfIsland(grid2) << endl << endl;
cout << "【测试用例3】单格陆地场景" << endl;
vector<vector<int>> grid3 = {{1}};
cout << "原始网格:" << endl;
printGrid(grid3);
cout << "最大岛屿面积:" << sol.maxAreaOfIsland(grid3) << endl;
return 0;
}

5.被围绕的区域(OJ题)--BFS解决FloodFill算法

算法思路:
正难则反.可以先利⽤ bfs 将与边缘相连的 '0' 区域做上标记,然后重新遍历矩阵,将没有标记过的'0'修改成'X'即可.
🔥实战核心剖析
1. 核心思想:FloodFill 逆向解题
这是本题最具实战价值的考点,也是BFS场景的关键变形:
- 常规思路 :寻找被包围的
O→ 难度极高,无法直接判断; - 逆向思路 :寻找与边界连通的
O→ 标记保护,剩余的必然是被包围的;
2. 实战优化技巧 - 原地标记法
用字符.替代独立的vis访问数组,空间复杂度优化至 O(1),是网格BFS的顶级优化; - 边界优先遍历
仅遍历4条边界的起点,而非整个网格,大幅减少无效计算; - 模板极致复用
完全沿用前文BFS队列、四方向、边界校验的通用模板,体现算法体系化思维.
3. 边界处理(BFS网格题通用准则) - 坐标严格限制在
[0,m)、[0,n)内,杜绝数组越界; - 仅处理未标记的
O,避免重复遍历; - 边界
O为BFS起点,是区分安全/被包围的唯一依据.
4. 执行流程 - 标记 :从边界
O出发,BFS 标记所有连通的O为.; - 替换 :剩余的
O都是被包围的,改为X; - 还原 :将临时标记
.还原为O.
核心代码
cpp
class Solution
{
//1.BFS网格通用模板:四方向偏移量(右、左、下、上)
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();
//2.核心逆向思维:优先保护边界连通的O
//思路:直接找被包围的O极难,反向标记「不会被包围的O」
//遍历上下两条边界
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++)
{
if(board[i][0] == 'O') bfs(board, i, 0);
if(board[i][n - 1] == 'O') bfs(board, i, n - 1);
}
//3.最终填充:完成替换与还原
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(board[i][j] == 'O') board[i][j] = 'X'; //被包围的O → 替换
else if(board[i][j] == '.') board[i][j] = 'O'; //安全的O → 还原
}
//4.BFS核心函数:FloodFill 标记安全区域
//功能:从边界O出发,扩散所有连通的O,标记为.(临时标记)
void bfs(vector<vector<char>>& board, int i, int j)
{
queue<pair<int, int>> q;
q.push({i, j});
board[i][j] = '.'; //入队即标记,原地修改,无需额外访问数组
//标准BFS逐层扩散
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];
//边界合法性校验(BFS网格题必写!)
if(x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'O')
{
q.push({x, y});
board[x][y] = '.';
}
}
}
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class Solution
{
//四方向偏移量:右、左、下、上(BFS网格遍历通用模板)
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();
//1.逆向思维:先处理边界上的 'O' 联通块,全部修改成 '.'(标记安全区域)
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++)
{
if(board[i][0] == 'O') bfs(board, i, 0);
if(board[i][n - 1] == 'O') bfs(board, i, n - 1);
}
//2.还原填充:被包围的O变X,安全的.还原为O
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(board[i][j] == 'O') board[i][j] = 'X';
else if(board[i][j] == '.') board[i][j] = 'O';
}
//BFS核心:FloodFill扩散标记所有与边界连通的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.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 < m && y >= 0 && y < n && board[x][y] == 'O')
{
q.push({x, y});
board[x][y] = '.';
}
}
}
}
};
void printBoard(const vector<vector<char>>& board) {
for (const auto& row : board) {
for (char ch : row) {
cout << ch << " ";
}
cout << endl;
}
cout << "---------------------" << endl;
}
int main() {
Solution sol;
cout << "【测试用例1】标准场景(边界O保留,内部O被替换)" << endl;
vector<vector<char>> board1 = {
{'X','X','X','X'},
{'X','O','O','X'},
{'X','X','O','X'},
{'X','O','X','X'}
};
cout << "填充前矩阵:" << endl;
printBoard(board1);
sol.solve(board1);
cout << "填充后矩阵:" << endl;
printBoard(board1);
cout << "【测试用例2】边界全O场景(无被包围区域)" << endl;
vector<vector<char>> board2 = {
{'O','O','O'},
{'O','O','O'},
{'O','O','O'}
};
cout << "填充前矩阵:" << endl;
printBoard(board2);
sol.solve(board2);
cout << "填充后矩阵:" << endl;
printBoard(board2);
cout << "【测试用例3】全X场景" << endl;
vector<vector<char>> board3 = {
{'X','X'},
{'X','X'}
};
cout << "填充前矩阵:" << endl;
printBoard(board3);
sol.solve(board3);
cout << "填充后矩阵:" << endl;
printBoard(board3);
return 0;
}

6.迷宫中离入口最近的出口(OJ题)--BFS解决最短路径问题

算法思路:解法(BFS求最短路):
利⽤层序遍历来解决迷宫问题,是最经典的做法.
我们可以从起点开始层序遍历,并且在遍历的过程中记录当前遍历的层数.这样就能在找到出⼝的时候,得到起点到出⼝的最短距离.
🔥关键实战技巧
- 层级遍历固定模板
cpp
int sz = q.size();
for(int i=0; i<sz; i++){ ... }
-
原地出口
直接判断坐标是否在迷宫边界,无需额外存储出口位置,简化:
-
提前返回优化
BFS首次到达出口即为最短路径,直接返回结果,无需冗余遍历,效率拉满"
-
通用骨架复用
四方向数组、边界校验、队列遍历,完全复用FloodFill的BFS基础模板,体现一套骨架适配多场景的实战规律.
-
边界处理(BFS通用准则)
坐标不越界:
x ∈ [0,m), y ∈ [0,n);仅遍历通路:
maze[x][y] == '.';禁止重复访问:
vis[x][y]标记,避免死循环/超时;出口排除入口:入口本身在边界,不算有效出口.
核心代码
cpp
class Solution
{
//四方向偏移量:上下左右(BFS网格通用模板,复用FloodFill核心骨架)
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
int nearestExit(vector<vector<char>>& maze, vector<int>& e)
{
int m = maze.size(), n = maze[0].size(); //迷宫行列数
bool vis[m][n]; //访问标记数组,防止重复遍历(实战必备)
memset(vis, 0, sizeof vis); //初始化访问数组,清空脏数据
//BFS核心容器:队列存储坐标,单源起点入队
queue<pair<int, int>> q;
q.push({e[0], e[1]}); //入口坐标入队
vis[e[0]][e[1]] = true; //标记入口已访问
int step = 0; //记录最短路径步数
//最短路径核心:层级BFS(按层遍历,每层对应一步)
while (q.size())
{
step++; //进入下一层,步数+1
int sz = q.size(); // 记录当前层的节点数量(层级遍历关键)
//遍历当前层所有节点
for (int i = 0; i < sz; i++)
{
auto [a, b] = q.front();
q.pop();
//四方向探索相邻位置
for (int j = 0; j < 4; j++)
{
int x = a + dx[j], y = b + dy[j];
//边界校验:不越界 + 是通路(.) + 未访问
if (x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == '.' && !vis[x][y])
{
//出口判定:到达迷宫边界(且不是入口),直接返回当前步数(最短路径)
if (x == 0 || x == m - 1 || y == 0 || y == n - 1)
return step;
q.push({x, y});
vis[x][y] = true;
}
}
}
}
// 遍历完无出口,返回-1
return -1;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class Solution
{
//四方向偏移量:右、左、下、上(BFS网格通用模板)
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
int nearestExit(vector<vector<char>>& maze, vector<int>& e)
{
int m = maze.size(), n = maze[0].size();
bool vis[m][n];
memset(vis, 0, sizeof vis);
queue<pair<int, int>> q;
q.push({e[0], e[1]});
vis[e[0]][e[1]] = true;
int step = 0;
//层级BFS:无权图最短路径核心模板
while (q.size())
{
step++;
int sz = q.size();
for (int i = 0; i < sz; i++)
{
auto [a, b] = q.front();
q.pop();
for (int j = 0; j < 4; j++)
{
int x = a + dx[j], y = b + dy[j];
//合法性校验:不越界 + 通路 + 未访问
if (x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == '.' && !vis[x][y])
{
// 到达出口:迷宫边界,直接返回最短步数
if (x == 0 || x == m - 1 || y == 0 || y == n - 1)
return step;
q.push({x, y});
vis[x][y] = true;
}
}
}
}
//无出口返回-1
return -1;
}
};
void printMaze(const vector<vector<char>>& maze) {
for (const auto& row : maze) {
for (char ch : row) {
cout << ch << " ";
}
cout << endl;
}
cout << "---------------------" << endl;
}
int main() {
Solution sol;
cout << "【测试用例1】标准迷宫场景" << endl;
vector<vector<char>> maze1 = {
{'+','+','.','+'},
{'.','.','.','+'},
{'+','+','+','.'}
};
vector<int> entrance1 = {1, 0}; // 入口坐标
cout << "迷宫矩阵:" << endl;
printMaze(maze1);
cout << "最短出口步数:" << sol.nearestExit(maze1, entrance1) << endl << endl;
cout << "【测试用例2】边界入口场景" << endl;
vector<vector<char>> maze2 = {
{'.','.'},
{'+','+'}
};
vector<int> entrance2 = {0, 0};
cout << "迷宫矩阵:" << endl;
printMaze(maze2);
cout << "最短出口步数:" << sol.nearestExit(maze2, entrance2) << endl << endl;
cout << "【测试用例3】无出口场景" << endl;
vector<vector<char>> maze3 = {
{'+','+','+'},
{'+','.','+'},
{'+','+','+'}
};
vector<int> entrance3 = {1, 1};
cout << "迷宫矩阵:" << endl;
printMaze(maze3);
cout << "最短出口步数:" << sol.nearestExit(maze3, entrance3) << endl;
return 0;
}

7.最小基因变化(OJ题)--BFS解决最短路径问题

算法思路:
如果将每次字符串的变换抽象成图中的两个顶点和⼀条边的话,问题就变成了边权为1的最短路问题.因此,从起始的字符串开始,来⼀次bfs即可.
🔥实战核心逻辑拆解
- 核心思想:状态转移 + 层级BFS
- 迷宫 :坐标
(x,y)是状态,上下左右移动是转移; - 基因变异:字符串是状态,单字符修改是转移;
- 共性 :无权最短路径 → 层级BFS → 首次到达即最短.
- 实战优化技巧
- 哈希集合加速
用unordered_set存储基因库和访问记录,查找效率O(1),远优于vector遍历,是字符串BFS的标配优化; - 层级遍历模板
固定队列大小sz,严格按层遍历,保证步数统计精准(BFS最短路径灵魂); - 原地状态修改
备份字符串逐位变异,无额外空间开销,代码简洁高效; - 提前剪枝返回
找到目标立即返回,无需冗余遍历,时间复杂度最优.
- 边界处理(BFS通用准则)
- 起点与终点相同,直接返回0;
- 目标基因不在库中,无合法路径;
- 仅处理基因库内+未访问的状态,杜绝死循环/重复计算;
- 基因长度固定为8,枚举所有位置无越界.
核心代码
cpp
class Solution
{
public:
int minMutation(string startGene, string endGene, vector<string>& bank)
{
unordered_set<string> vis; //标记已访问的基因状态,避免重复搜索
unordered_set<string> hash(bank.begin(), bank.end()); //基因库:哈希集合O(1)快速查找
string change = "ACGT"; //基因可变异的4种字符
//边界条件1:起点 == 终点,变异次数为0
if (startGene == endGene)
return 0;
//边界条件2:目标基因不在基因库中,直接返回-1
if (!hash.count(endGene))
return -1;
//BFS 初始化:起点入队,标记访问
queue<string> q;
q.push(startGene);
vis.insert(startGene);
int ret = 0; // 记录最短变异步数
//核心:层级BFS(最短路径专属模板)
while (q.size())
{
ret++; //进入下一层,变异步数+1
int sz = q.size(); //固定当前层节点数,严格层级遍历
//遍历当前层所有基因状态
while (sz--)
{
string t = q.front();
q.pop();
//枚举基因的8个位置(基因长度固定为8)
for (int i = 0; i < 8; i++)
{
string tmp = t; //备份原字符串,逐位修改,不破坏原数据
//枚举当前位置的4种变异可能:A/C/G/T
for (int j = 0; j < 4; j++)
{
tmp[i] = change[j];
//校验:新基因在基因库中 + 未被访问过
if (hash.count(tmp) && !vis.count(tmp))
{
//找到目标基因,直接返回当前步数(最短路径)
if (tmp == endGene)
return ret;
//合法状态:入队 + 标记访问
q.push(tmp);
vis.insert(tmp);
}
}
}
}
}
//遍历完所有状态,无法到达目标
return -1;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_set>
#include <string>
using namespace std;
class Solution
{
public:
int minMutation(string startGene, string endGene, vector<string>& bank)
{
unordered_set<string> vis; //标记已经搜索过的状态
unordered_set<string> hash(bank.begin(), bank.end()); //基因库哈希集合
string change = "ACGT"; //可变异的基因字符
//边界条件:起点等于终点
if (startGene == endGene)
return 0;
//边界条件:目标基因不在库中
if (!hash.count(endGene))
return -1;
queue<string> q;
q.push(startGene);
vis.insert(startGene);
int ret = 0;
//层级BFS:无权状态图最短路径核心模板
while (q.size())
{
ret++;
int sz = q.size();
while (sz--)
{
string t = q.front();
q.pop();
//枚举基因的8个位置
for (int i = 0; i < 8; i++)
{
string tmp = t;
//枚举4种变异字符
for (int j = 0; j < 4; j++)
{
tmp[i] = change[j];
//合法状态:在基因库中 + 未访问
if (hash.count(tmp) && !vis.count(tmp))
{
if (tmp == endGene)
return ret;
q.push(tmp);
vis.insert(tmp);
}
}
}
}
}
return -1;
}
};
int main() {
Solution sol;
cout << "【测试用例1】标准基因变异场景" << endl;
string start1 = "AACCGGTT";
string end1 = "AACCGGTA";
vector<string> bank1 = {"AACCGGTA","AACCGCTA","AAACGGTA"};
cout << "起始基因:" << start1 << " 目标基因:" << end1 << endl;
cout << "最小变异步数:" << sol.minMutation(start1, end1, bank1) << endl << endl;
cout << "【测试用例2】边界场景:起点与目标基因相同" << endl;
string start2 = "AAAAACDD";
string end2 = "AAAAACDD";
vector<string> bank2 = {"AAAAACDD"};
cout << "起始基因:" << start2 << " 目标基因:" << end2 << endl;
cout << "最小变异步数:" << sol.minMutation(start2, end2, bank2) << endl << endl;
cout << "【测试用例3】无有效变异路径场景" << endl;
string start3 = "AAAAAACC";
string end3 = "AACCGGTT";
vector<string> bank3 = {"AAAAAACC","AAAAGGCC"};
cout << "起始基因:" << start3 << " 目标基因:" << end3 << endl;
cout << "最小变异步数:" << sol.minMutation(start3, end3, bank3) << endl << endl;
cout << "【测试用例4】目标基因不在基因库场景" << endl;
string start4 = "AACCGGTT";
string end4 = "AACCGGAA";
vector<string> bank4 = {"AACCGGTA","AACCGCTA"};
cout << "起始基因:" << start4 << " 目标基因:" << end4 << endl;
cout << "最小变异步数:" << sol.minMutation(start4, end4, bank4) << endl;
return 0;
}

8.单词接龙(OJ题)--BFS解决最短路径问题

核心逻辑拆解
- 层级BFS:最短路径的灵魂
这是本题与FloodFill 的核心区别,也是所有最短路径题的唯一解法:
- FloodFill:仅需扩散遍历连通域,无步数要求;
- 最短路径 :必须按层遍历 ,每一层对应一次单词转换 ,第一次到达终点的层数就是最短步数.
- 状态转移:BFS从网格到字符串的迁移
- 网格BFS:状态 = 坐标
(x,y),转移 = 上下左右移动; - 单词接龙:状态 = 字符串,转移 = 单字符替换;
- 共性规律 :BFS 只关心状态扩散,不关心状态的具体形式.
- 实战优化技巧
- 哈希集合加速
unordered_set实现O(1)查找,是字符串BFS的标配优化,避免超时; - 层级遍历固定模板
sz = q.size()严格控制每层遍历范围,保证步数统计100%精准; - 提前剪枝返回
找到目标立即返回,无冗余遍历,时间复杂度最优; - 原地状态修改
备份字符串逐位替换,无额外空间开销,代码极简.
- 边界处理(BFS通用准则)
- 目标单词不在单词库 → 直接返回0;
- 访问标记去重 → 杜绝死循环/重复计算;
- 枚举所有字符/位置 → 无状态遗漏.
核心代码
cpp
class Solution
{
public:
//求解单词接龙最短转换长度
int ladderLength(string beginWord, string endWord, vector<string>& wordList)
{
//1.实战优化:哈希集合存储单词库 → O(1)快速查找(替代vector暴力遍历)
unordered_set<string> hash(wordList.begin(), wordList.end());
//访问标记集合:避免重复搜索同一单词(BFS通用去重手段)
unordered_set<string> vis;
//2.边界剪枝(实战必备):目标单词不在库中 → 直接返回0
if (!hash.count(endWord))
return 0;
//3.BFS 初始化:标准单源BFS起步
queue<string> q; //队列存储:待搜索的单词状态
q.push(beginWord); //起始单词入队
vis.insert(beginWord); //标记已访问
int ret = 1; //步数初始为1:序列包含起始单词本身
//4.核心:层级BFS(最短路径专属模板,全文通用!)
while (q.size())
{
ret++; //进入下一层 = 转换一次单词,步数+1
int sz = q.size(); //固定当前层节点数,严格层级遍历
//遍历当前层所有状态
while (sz--)
{
string t = q.front();
q.pop();
//5.状态转移核心:枚举单词的每一位字符
for (int i = 0; i < t.size(); i++)
{
string tmp = t; //备份原单词,逐位修改
//枚举26个小写字母,生成所有可能的转换状态
for (char ch = 'a'; ch <= 'z'; ch++)
{
tmp[i] = ch; //替换第i位字符
//6.合法性校验:新单词在库中 + 未被访问
if (hash.count(tmp) && !vis.count(tmp))
{
//找到目标单词:直接返回当前步数(最短路径)
if (tmp == endWord)
return ret;
//合法状态:入队 + 标记访问
q.push(tmp);
vis.insert(tmp);
}
}
}
}
}
//遍历完所有状态,无有效路径 → 返回0
return 0;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_set>
#include <string>
using namespace std;
class Solution
{
public:
int ladderLength(string beginWord, string endWord,
vector<string>& wordList)
{
//哈希集合:O(1)快速查询单词是否存在
unordered_set<string> hash(wordList.begin(), wordList.end());
unordered_set<string> vis; //标记已访问的单词,避免重复搜索
//边界剪枝:目标单词不在词库中,直接返回0
if (!hash.count(endWord))
return 0;
//BFS初始化:起点入队,标记访问
queue<string> q;
q.push(beginWord);
vis.insert(beginWord);
int ret = 1; //初始长度为1(包含起始单词)
//层级BFS:无权状态图最短路径核心模板
while (q.size())
{
ret++; //进入下一层,转换序列长度+1
int sz = q.size();
//遍历当前层所有单词
while (sz--)
{
string t = q.front();
q.pop();
//枚举单词的每一位字符
for (int i = 0; i < t.size(); i++)
{
string tmp = t;
//枚举26个小写字母,生成所有可能的转换
for (char ch = 'a'; ch <= 'z'; ch++)
{
tmp[i] = ch;
//校验:单词存在 + 未访问
if (hash.count(tmp) && !vis.count(tmp))
{
//找到目标,返回最短长度
if (tmp == endWord)
return ret;
q.push(tmp);
vis.insert(tmp);
}
}
}
}
}
//无有效路径,返回0
return 0;
}
};
void printWordList(const vector<string>& wordList) {
cout << "单词库:[";
for (int i = 0; i < wordList.size(); i++) {
if (i > 0) cout << ", ";
cout << wordList[i];
}
cout << "]" << endl;
}
int main() {
Solution sol;
cout << "【测试用例1】标准单词接龙场景" << endl;
string begin1 = "hit";
string end1 = "cog";
vector<string> wordList1 = {"hot","dot","dog","lot","log","cog"};
cout << "起始单词:" << begin1 << " 目标单词:" << end1 << endl;
printWordList(wordList1);
cout << "最短转换序列长度:" << sol.ladderLength(begin1, end1, wordList1) << endl << endl;
cout << "【测试用例2】无转换路径场景" << endl;
string begin2 = "hit";
string end2 = "cog";
vector<string> wordList2 = {"hot","dot","lot","log"};
cout << "起始单词:" << begin2 << " 目标单词:" << end2 << endl;
printWordList(wordList2);
cout << "最短转换序列长度:" << sol.ladderLength(begin2, end2, wordList2) << endl << endl;
cout << "【测试用例3】目标单词不存在场景" << endl;
string begin3 = "hit";
string end3 = "cog";
vector<string> wordList3 = {"hot","dot","dog"};
cout << "起始单词:" << begin3 << " 目标单词:" << end3 << endl;
printWordList(wordList3);
cout << "最短转换序列长度:" << sol.ladderLength(begin3, end3, wordList3) << endl << endl;
cout << "【测试用例4】一步转换场景" << endl;
string begin4 = "hot";
string end4 = "dot";
vector<string> wordList4 = {"dot","dog"};
cout << "起始单词:" << begin4 << " 目标单词:" << end4 << endl;
printWordList(wordList4);
cout << "最短转换序列长度:" << sol.ladderLength(begin4, end4, wordList4) << endl;
return 0;
}

9.为高尔夫比赛砍树(OJ题)--BFS解决最短路径问题

算法思路:解法:
(1)先找出砍树的顺序;
(2)然后按照砍树的顺序,⼀个⼀个的⽤ bfs 求出最短路即可.
核心实战拆解
-
场景拆解:从复杂问题到BFS基础场景
本题是复合场景,拆解后完全回归BFS基础能力:
-
排序预处理:按规则确定遍历顺序;
-
单源最短路径:两点之间的最短距离 → 层级BFS;
-
路径累加:多次BFS结果求和,得到总最小步数.
-
核心逻辑:层级BFS
- FloodFill:仅标记连通域,无步数要求;
- 最短路径 :按层遍历 ,每一层对应一步,第一次到达终点的步数就是最小值.
- 实战技巧
- 全局变量简化 :
m/n/vis/dx/dy全局化,省去反复传参; - 每次BFS重置访问数组 :
memset清空脏数据,避免错误; - 提前剪枝 :任意一段路径不通,直接返回
-1,无冗余计算; - 入队即标记:杜绝节点重复入队,避免超时/死循环.
- 边界处理
- 坐标不越界:
x∈[0,m), y∈[0,n); - 障碍判断:
f[x][y] = 0无法通行; - 起点终点重合:步数直接为
0; - 访问标记:保证每个格子只遍历一次.
核心代码
cpp
class Solution
{
//全局变量:简化BFS传参(BFS网格题实战优化技巧)
int m, n; //网格的行数、列数
bool vis[51][51]; //访问标记数组(题目最大网格50x50)
int dx[4] = {0, 0, 1, -1}; //四方向偏移量:右、左、下、上(BFS通用骨架)
int dy[4] = {1, -1, 0, 0};
public:
//总逻辑(砍树总步数计算)
int cutOffTree(vector<vector<int>>& f)
{
m = f.size(), n = f[0].size();
//1.准备工作:收集所有需要砍伐的树(值>1代表树,0是障碍,1是平地)
vector<pair<int, int>> trees;
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(f[i][j] > 1)
trees.push_back({i, j});
//2.核心规则:按树的高度**从小到大排序**(必须从矮到高砍树)
sort(trees.begin(), trees.end(), [&](const pair<int, int>& p1, const pair<int, int>& p2)
{
return f[p1.first][p1.second] < f[p2.first][p2.second];
});
//3.按顺序砍树,累加每段最短路径
int bx = 0, by = 0; //起点:初始位置(0,0)
int ret = 0; //总步数
for(auto& [a, b] : trees)
{
//BFS计算:当前位置 → 下一棵树的最短步数
int step = bfs(f, bx, by, a, b);
if(step == -1) //任意一段无法到达,直接返回-1
return -1;
ret += step; //累加步数
bx = a, by = b; //更新当前位置为刚砍完的树的位置
}
return ret; //返回总最小步数
}
//BFS核心函数:求解 起点(bx,by) → 终点(ex,ey) 的最短路径(无权网格标配)
int bfs(vector<vector<int>>& f, int bx, int by, int ex, int ey)
{
//边界剪枝:起点=终点,步数为0
if(bx == ex && by == ey)
return 0;
queue<pair<int, int>> q; //BFS队列:存储坐标
memset(vis, 0, sizeof vis); //每次BFS重置访问数组(实战必备)
q.push({bx, by});
vis[bx][by] = true; //入队即标记,避免重复入队
int step = 0; //记录路径步数
//层级BFS:无权图最短路径的灵魂(按层遍历,首次到达即最短)
while(q.size())
{
step++;
int sz = q.size(); //固定当前层节点数,严格层级遍历
while(sz--)
{
auto [a, b] = q.front();
q.pop();
//四方向扩散(BFS通用遍历逻辑)
for(int i = 0; i < 4; i++)
{
int x = a + dx[i], y = b + dy[i];
//合法性校验(BFS网格题必写):
//不越界 + 不是障碍(f[x][y]≠0) + 未访问
if(x >= 0 && x < m && y >= 0 && y < n && f[x][y] && !vis[x][y])
{
//到达终点:直接返回当前步数(最短路径)
if(x == ex && y == ey)
return step;
q.push({x, y});
vis[x][y] = true;
}
}
}
}
return -1; //遍历完无路径,返回-1
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
class Solution
{
int m, n;
public:
int cutOffTree(vector<vector<int>>& f)
{
m = f.size(), n = f[0].size();
//1.准备工作:找出砍树的顺序
vector<pair<int, int>> trees;
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(f[i][j] > 1)
trees.push_back({i, j});
//按树的高度从小到大排序
sort(trees.begin(), trees.end(), [&](const pair<int, int>& p1, const pair<int, int>& p2)
{
return f[p1.first][p1.second] < f[p2.first][p2.second];
});
//2.按照顺序砍树,累加最短路径
int bx = 0, by = 0;
int ret = 0;
for(auto& [a, b] : trees)
{
int step = bfs(f, bx, by, a, b);
if(step == -1)
return -1;
ret += step;
bx = a, by = b;
}
return ret;
}
bool vis[51][51];
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
//BFS:求解两点间最短路径
int bfs(vector<vector<int>>& f, int bx, int by, int ex, int ey)
{
if(bx == ex && by == ey)
return 0;
queue<pair<int, int>> q;
memset(vis, 0, sizeof vis);
q.push({bx, by});
vis[bx][by] = true;
int step = 0;
while(q.size())
{
step++;
int sz = q.size();
while(sz--)
{
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 && x < m && y >= 0 && y < n && f[x][y] && !vis[x][y])
{
if(x == ex && y == ey)
return step;
q.push({x, y});
vis[x][y] = true;
}
}
}
}
return -1;
}
};
int main() {
Solution sol;
vector<vector<int>> forest1 = {
{1,2,3},
{0,0,4},
{7,6,5}
};
cout << "测试用例1结果:" << sol.cutOffTree(forest1) << "(预期:6)" << endl;
vector<vector<int>> forest2 = {
{1,2,3},
{0,0,0},
{7,6,5}
};
cout << "测试用例2结果:" << sol.cutOffTree(forest2) << "(预期:-1)" << endl;
vector<vector<int>> forest3 = {
{1,3},
{2,4}
};
cout << "测试用例3结果:" << sol.cutOffTree(forest3) << "(预期:4)" << endl;
vector<vector<int>> forest4 = {{1}};
cout << "测试用例4结果:" << sol.cutOffTree(forest4) << "(预期:0)" << endl;
return 0;
}

10.01矩阵(OJ题)--多源BFS问题

算法思路:解法(bfs)(多个源头的最短路问题)
对于求的最终结果,我们有两种⽅式:
- 第⼀种⽅式:从每⼀个1开始,然后通过层序遍历找到离它最近的0.这⼀种⽅式,我们会以所有的1起点,来⼀次层序遍历,势必会遍历到很多重复的点.并且如果矩阵中只有⼀个0的话,每⼀次层序遍历都要遍历很多层,时间复杂度较⾼.
- 换⼀种⽅式:从 0 开始层序遍历,并且记录遍历的层数.当第⼀次碰到1的时候,当前的层数就是这个 1 离0的最短距离.这⼀种⽅式,我们在遍历的时候标记⼀下处理过的1,能够做到只⽤遍历整个矩阵⼀次,就能得到最终结果.但是,这⾥有⼀个问题,0是有很多个的,我们怎么才能保证遇到的1距离这⼀个0是最近的呢?其实很简单,我们可以先把所有的0都放在队列中,把它们当成⼀个整体,每次把当前队列⾥⾯的所有元素向外扩展⼀次.
核心概念:单源BFS vs 多源BFS
单源BFS :从1个起点 出发,找终点的最短路径.
多源BFS :从多个起点同时出发 ,一起向外层扩散遍历,第一次到达的位置就是最短距离.
通俗比喻
- 单源BFS:1个人从起点出发找目标
- 多源BFS:一群人从不同起点同时出发找目标,谁先到谁就是最短路径
多源BFS 核心原理
- BFS 是层序遍历 (一层一层向外走),天然适合求最短路径
- 多源BFS 把所有起点当作第0层,一次性全部加入队列
- 同步扩散,保证每个位置只会被最近的起点最先到达,效率极高
时间优势
如果用单源BFS遍历N个起点:时间复杂度 O ( N × M ) O(N \times M) O(N×M)
多源BFS:只遍历一次地图,时间复杂度 O ( M ) O(M) O(M)(M是格子总数)
多源BFS 标准解题步骤
- 初始化队列 :遍历地图,把所有起点坐标入队,同时标记访问/记录距离
- 方向数组:上下左右(四方向)/八方向
- 层序BFS:队列循环,逐层扩散,更新距离/状态
- 结果处理:统计答案
核心代码
cpp
class Solution
{
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
vector<vector<int>> updateMatrix(vector<vector<int>>& mat)
{
int m = mat.size(), n = mat[0].size();
//dist[i][j] == -1 表⽰:没有搜索过
//dist[i][j] != -1 表⽰:最短距离
vector<vector<int>> dist(m, vector<int>(n, -1));
queue<pair<int, int>> q;
//1.把所有的源点加⼊到队列中
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
if (mat[i][j] == 0)
{
q.push({i, j});
dist[i][j] = 0;
}
//2.⼀层⼀层的往外扩
while (q.size())
{
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 && x < m && y >= 0 && y < n && dist[x][y] == -1)
{
dist[x][y] = dist[a][b] + 1;
q.push({x, y});
}
}
}
return dist;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class Solution
{
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
vector<vector<int>> updateMatrix(vector<vector<int>>& mat)
{
int m = mat.size(), n = mat[0].size();
//dist[i][j] == -1 表⽰:没有搜索过
//dist[i][j] != -1 表⽰:最短距离
vector<vector<int>> dist(m, vector<int>(n, -1));
queue<pair<int, int>> q;
//1.把所有的源点加⼊到队列中
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
if (mat[i][j] == 0)
{
q.push({i, j});
dist[i][j] = 0;
}
//2.⼀层⼀层的往外扩
while (q.size())
{
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 && x < m && y >= 0 && y < n && dist[x][y] == -1)
{
dist[x][y] = dist[a][b] + 1;
q.push({x, y});
}
}
}
return dist;
}
};
void printMatrix(const vector<vector<int>>& matrix) {
for (const auto& row : matrix) {
for (int num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "-------------------------" << endl;
}
int main() {
Solution sol;
vector<vector<int>> mat1 = {
{0,0,0},
{0,1,0},
{1,1,1}
};
cout << "测试用例1 输入矩阵:" << endl;
printMatrix(mat1);
cout << "测试用例1 输出结果(最近0的距离):" << endl;
printMatrix(sol.updateMatrix(mat1));
vector<vector<int>> mat2 = {
{0,0,0},
{0,0,0},
{0,0,0}
};
cout << "测试用例2 输入矩阵:" << endl;
printMatrix(mat2);
cout << "测试用例2 输出结果:" << endl;
printMatrix(sol.updateMatrix(mat2));
vector<vector<int>> mat3 = {
{1,0,1},
{1,1,1},
{1,0,1}
};
cout << "测试用例3 输入矩阵:" << endl;
printMatrix(mat3);
cout << "测试用例3 输出结果:" << endl;
printMatrix(sol.updateMatrix(mat3));
return 0;
}

11.飞驰的数量(OJ题)--多源BFS问题

算法思路:解法(正难则反):
从边上的1开始搜索,把与边上1相连的联通区域全部标记⼀下;然后再遍历⼀遍矩阵,看看哪些位置的1没有被标记即可标记的时候,可以⽤多源BFS解决.
多源 BFS 起点:所有网格边界上的陆地(这些陆地能直接出界,肯定不是飞地)
扩散标记:从边界陆地出发,遍历所有连通的陆地,全部标记为非飞地
统计结果:剩下没被标记的陆地 = 飞地,计数即可
核心代码
cpp
class Solution
{
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
int numEnclaves(vector<vector<int>>& grid)
{
int m = grid.size(), n = grid[0].size();
vector<vector<bool>> vis(m, vector<bool>(n));
queue<pair<int, int>> q;
//1.把边上的 1 加⼊到队列中
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
if (i == 0 || i == m - 1 || j == 0 || j == n - 1)
{
if (grid[i][j] == 1)
{
q.push({i, j});
vis[i][j] = true;
}
}
//2.多源 bfs
while (q.size())
{
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 && x < m && y >= 0 && y < n && grid[x][y] == 1 && !vis[x][y])
{
vis[x][y] = true;
q.push({x, y});
}
}
}
//3.统计结果
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++;
return ret;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class Solution
{
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
int numEnclaves(vector<vector<int>>& grid)
{
int m = grid.size(), n = grid[0].size();
vector<vector<bool>> vis(m, vector<bool>(n));
queue<pair<int, int>> q;
//1.把边上的 1 加⼊到队列中
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
if (i == 0 || i == m - 1 || j == 0 || j == n - 1)
{
if (grid[i][j] == 1)
{
q.push({i, j});
vis[i][j] = true;
}
}
//2.多源 bfs
while (q.size())
{
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 && x < m && y >= 0 && y < n && grid[x][y] == 1 && !vis[x][y])
{
vis[x][y] = true;
q.push({x, y});
}
}
}
//3.统计结果
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++;
return ret;
}
};
void printGrid(const vector<vector<int>>& grid) {
for (const auto& row : grid) {
for (int num : row) {
cout << num << " ";
}
cout << endl;
}
}
int main() {
Solution sol;
vector<vector<int>> grid1 = {
{0,0,0,0},
{1,0,1,0},
{0,1,1,0},
{0,0,0,0}
};
cout << "=== 测试用例1 ===" << endl;
cout << "输入网格:" << endl;
printGrid(grid1);
cout << "飞地数量:" << sol.numEnclaves(grid1) << "(预期:3)" << endl << endl;
vector<vector<int>> grid2 = {
{0,1,0},
{1,1,1},
{0,1,0}
};
cout << "=== 测试用例2 ===" << endl;
cout << "输入网格:" << endl;
printGrid(grid2);
cout << "飞地数量:" << sol.numEnclaves(grid2) << "(预期:0)" << endl << endl;
vector<vector<int>> grid3 = {
{0,0,0},
{0,0,0},
{0,0,0}
};
cout << "=== 测试用例3 ===" << endl;
cout << "输入网格:" << endl;
printGrid(grid3);
cout << "飞地数量:" << sol.numEnclaves(grid3) << "(预期:0)" << endl << endl;
vector<vector<int>> grid4 = {
{1,1,1},
{1,0,1},
{1,1,1}
};
cout << "=== 测试用例4 ===" << endl;
cout << "输入网格:" << endl;
printGrid(grid4);
cout << "飞地数量:" << sol.numEnclaves(grid4) << "(预期:0)" << endl;
return 0;
}

12.地图中的最高点(OJ题)--多源BFS问题

算法思路:解法(多源BFS):
核心原理
陆地的最大可能高度 = 这个位置到最近水域的距离→ 用多源 BFS,把所有水域当作起点,向外扩散计算距离,就是答案!
dist 矩阵:双重用途
标记是否处理:-1 = 还没计算高度
存储结果:最终的高度值
q:BFS 队列,用于层序扩散
关键:
这是多源 BFS和单源 BFS 的唯一区别:
遍历整个矩阵,把所有水域当作起点,一次性全部加入队列;
水域高度固定为0,满足题目硬性要求.
执行逻辑:
所有水域同时向外扩散(层序遍历)
每扩散一层,高度 +1,严格满足相邻高度差为 1
第一次到达陆地时,就是到最近水域的最短距离(即最大高度)
保证了高度最大且合法.
核心代码
cpp
class Solution
{
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
vector<vector<int>> highestPeak(vector<vector<int>>& isWater)
{
int m = isWater.size(), n = isWater[0].size();
vector<vector<int>> dist(m, vector<int>(n, -1));
queue<pair<int, int>> q;
// 1. 把所有的源点加⼊到队列⾥⾯
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(isWater[i][j])
{
dist[i][j] = 0;
q.push({i, j});
}
// 2. 多源 bfs
while(q.size())
{
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 && x < m && y >= 0 && y < n && dist[x][y] == -1)
{
dist[x][y] = dist[a][b] + 1;
q.push({x, y});
}
}
}
return dist;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class Solution
{
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
vector<vector<int>> highestPeak(vector<vector<int>>& isWater)
{
int m = isWater.size(), n = isWater[0].size();
vector<vector<int>> dist(m, vector<int>(n, -1));
queue<pair<int, int>> q;
// 1. 把所有的源点加入到队列里面
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(isWater[i][j])
{
dist[i][j] = 0;
q.push({i, j});
}
// 2. 多源 bfs
while(q.size())
{
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 && x < m && y >= 0 && y < n && dist[x][y] == -1)
{
dist[x][y] = dist[a][b] + 1;
q.push({x, y});
}
}
}
return dist;
}
};
void printMatrix(const vector<vector<int>>& matrix) {
for (const auto& row : matrix) {
for (int num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "-------------------------" << endl;
}
int main() {
Solution sol;
vector<vector<int>> isWater1 = {
{0, 1},
{0, 0}
};
cout << "测试用例1 输入(水域矩阵):" << endl;
printMatrix(isWater1);
cout << "测试用例1 输出(高度矩阵):" << endl;
printMatrix(sol.highestPeak(isWater1));
vector<vector<int>> isWater2 = {
{0, 0, 1},
{1, 0, 0},
{0, 0, 0}
};
cout << "测试用例2 输入(水域矩阵):" << endl;
printMatrix(isWater2);
cout << "测试用例2 输出(高度矩阵):" << endl;
printMatrix(sol.highestPeak(isWater2));
vector<vector<int>> isWater3 = {
{1, 1},
{1, 1}
};
cout << "测试用例3 输入(全水域):" << endl;
printMatrix(isWater3);
cout << "测试用例3 输出(高度矩阵):" << endl;
printMatrix(sol.highestPeak(isWater3));
return 0;
}

13.地图分析(OJ题)--多源BFS问题

算法思路:解法(多源BFS):
核心思路
把所有陆地当作起点,用多源BFS 同时向外扩散,最后扩散到的海洋,就是最远的海洋,它的距离就是答案!
dist 矩阵:
-1=还没被扩散到
数字 = 当前格子到最近陆地的距离
q:BFS 队列,层序扩散
把地图上所有陆地当作起点,同时入队,同步扩散!
q.empty():全是海洋(没有陆地)→ 返回 - 1
q.size() == m*n:全是陆地(没有海洋)→ 返回 - 1
核心代码
cpp
class Solution
{
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
int maxDistance(vector<vector<int>>& grid)
{
int m = grid.size(), n = grid[0].size();
vector<vector<int>> dist(m, vector<int>(n, -1));
queue<pair<int, int>> q;
// 1. 初始化队列:将所有陆地 (1) 加入
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
if (grid[i][j])
{
dist[i][j] = 0;
q.push({i, j});
}
// 如果全是陆地,没有海洋,返回 -1
if (q.empty() || q.size() == m * n) return -1;
int ret = -1;
// 2. 多源 BFS 扩散
while (q.size())
{
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 && x < m && y >= 0 && y < n && dist[x][y] == -1)
{
dist[x][y] = dist[a][b] + 1;
q.push({x, y});
// 实时更新最大距离
ret = max(ret, dist[x][y]);
}
}
}
return ret;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
class Solution
{
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
public:
int maxDistance(vector<vector<int>>& grid)
{
int m = grid.size(), n = grid[0].size();
vector<vector<int>> dist(m, vector<int>(n, -1));
queue<pair<int, int>> q;
// 1. 初始化队列:将所有陆地 (1) 加入
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
if (grid[i][j])
{
dist[i][j] = 0;
q.push({i, j});
}
// 全海洋 / 全陆地,返回 -1
if (q.empty() || q.size() == m * n) return -1;
int ret = -1;
// 2. 多源 BFS 扩散
while (q.size())
{
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 && x < m && y >= 0 && y < n && dist[x][y] == -1)
{
dist[x][y] = dist[a][b] + 1;
q.push({x, y});
// 实时更新最大距离
ret = max(ret, dist[x][y]);
}
}
}
return ret;
}
};
void printGrid(const vector<vector<int>>& grid) {
for (const auto& row : grid) {
for (int num : row) {
cout << num << " ";
}
cout << endl;
}
}
int main() {
Solution sol;
vector<vector<int>> grid1 = {
{1, 0, 1},
{0, 0, 0},
{1, 0, 1}
};
cout << "=== 测试用例1 ===" << endl;
cout << "输入网格(1=陆地,0=海洋):" << endl;
printGrid(grid1);
cout << "最远距离:" << sol.maxDistance(grid1) << "(预期:2)" << endl << endl;
vector<vector<int>> grid2 = {
{1, 1},
{1, 1}
};
cout << "=== 测试用例2 ===" << endl;
cout << "输入网格:" << endl;
printGrid(grid2);
cout << "最远距离:" << sol.maxDistance(grid2) << "(预期:-1)" << endl << endl;
vector<vector<int>> grid3 = {
{0, 0},
{0, 0}
};
cout << "=== 测试用例3 ===" << endl;
cout << "输入网格:" << endl;
printGrid(grid3);
cout << "最远距离:" << sol.maxDistance(grid3) << "(预期:-1)" << endl << endl;
vector<vector<int>> grid4 = {
{1, 0},
{0, 0}
};
cout << "=== 测试用例4 ===" << endl;
cout << "输入网格:" << endl;
printGrid(grid4);
cout << "最远距离:" << sol.maxDistance(grid4) << "(预期:1)" << endl;
return 0;
}

14.课程表(OJ题)--BFS解决拓扑排序

前置知识介绍:
1.什么是拓扑排序?
针对有向无环图(DAG) ,把图中节点排成一个线性序列:满足:对于图中任意一条有向边u → v,顶点u 一定排在v前面.所有的边 前驱节点 → 后继节点,在序列中前驱一定出现在后继前面 .简单说:有依赖关系的任务,必须先做前置任务,再做后续任务 .
2.关键定义
- 入度:指向当前节点的边的数量(= 完成这个任务需要的前置任务数)
- 入度为 0:没有任何前置依赖,可以直接执行的节点
3.BFS 拓扑排序核心思想(Kahn 算法)
- 先做没有依赖的任务(入度=0)
- 做完后,解除它对后续任务的限制(后续任务入度-1)
- 重复执行,直到所有任务做完
- 若最后有任务没做完 → 图中有环 → 无法拓扑排序
BFS 拓扑排序标准 3 步骤
1. 建图 + 统计所有节点的入度
2. 初始化队列:所有入度=0 的节点入队
3. BFS 遍历:出队节点 → 相邻节点入度-1 → 入度=0 则入队
4. 验证结果:所有节点都被遍历 → 无环(拓扑排序有效)
AOV 网(Activity On Vertex Network)
AOV 网,全称顶点表示活动的网 ,是有向无环图(DAG) 的一种典型应用,专门用来描述工程、项目、任务之间的先后依赖关系 .
1.基本定义
- 用顶点(Vertex) 表示一个活动(Activity),比如工序、任务、课程、步骤等;
- 用有向边(Directed Edge) 表示活动之间的先后制约关系 :
若存在有向边<i, j>,则表示活动 i 必须先完成,活动 j 才能开始,即 i 是 j 的前驱,j 是 i 的后继。
简单记:
顶点 = 活动,边 = 先后顺序
2.AOV 网的核心性质
(1)必须是有向无环图(DAG)
不能出现环,否则会出现"活动 A 依赖 B,B 依赖 C,C 又依赖 A"的死循环,工程无法执行。
(2)活动之间存在偏序 / 全序关系
- 偏序:部分活动有先后,部分无先后,可以并行;
- 全序:所有活动都能排成一个严格的先后序列。
(3)一个合法的 AOV 网一定存在至少一种拓扑排序。
3.AOV 网与拓扑排序的关系
对 AOV 网进行拓扑排序 ,就是把所有活动排成一个线性序列,满足:
- 所有前驱活动排在前面,后继活动排在后面;
- 按这个序列执行,所有依赖关系都被满足,工程可以顺利完成.
所以:
AOV 网是问题模型,拓扑排序是求解该模型的方法
一句话总结:
AOV 网就是用有向无环图表示"谁必须先做、谁才能后做"的任务依赖模型,拓扑排序就是给它排一个合法的执行顺序.
算法思路:解法(原问题可以转换成⼀个拓扑排序问题,⽤ BFS 解决拓扑排序即可.)
拓扑排序流程:
(1)将所有⼊度为0 的点加⼊到队列中;
(2)当队列不空的时候,⼀直循环:
(3)取出队头元素;
(4)将于队头元素相连的顶点的⼊度- 1;
(5)然后判断是否减成0.如果减成0,就加⼊到队列中.
核心代码
cpp
class Solution
{
public:
bool canFinish(int n, vector<vector<int>>& p)
{
unordered_map<int, vector<int>> edges; //邻接表
vector<int> in(n); //存储每⼀个结点的⼊度
//1.建图
for(auto& e : p)
{
int a = e[0], b = e[1];
edges[b].push_back(a);
in[a]++;
}
//2.拓扑排序 - bfs
queue<int> q;
//把所有⼊度为 0 的点加⼊到队列中
for(int i = 0; i < n; i++)
{
if(in[i] == 0) q.push(i);
}
//层序遍历
while(!q.empty())
{
int t = q.front();
q.pop();
//修改相连的边
for(int e : edges[t])
{
in[e]--;
if(in[e] == 0) q.push(e);
}
}
//3.判断是否有环
for(int i : in)
if(i)
return false;
return true;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <unordered_map>
#include <queue>
using namespace std;
class Solution
{
public:
bool canFinish(int n, vector<vector<int>>& p)
{
unordered_map<int, vector<int>> edges; // 邻接表:存储图的边
vector<int> in(n); // 入度数组:记录每个节点的入度
// 1. 构建有向图 + 统计入度
// 题意:要学a,必须先学b → 建边 b -> a
for(auto& e : p)
{
int a = e[0], b = e[1];
edges[b].push_back(a); // b 指向 a
in[a]++; // a 的入度 +1
}
// 2. BFS 拓扑排序
queue<int> q;
// 初始化队列:所有入度为 0 的节点入队
for(int i = 0; i < n; i++)
{
if(in[i] == 0)
q.push(i);
}
// 层序遍历
while(!q.empty())
{
int t = q.front();
q.pop();
// 遍历当前节点的所有后继节点,入度-1
for(int e : edges[t])
{
in[e]--;
// 入度为0,加入队列
if(in[e] == 0)
q.push(e);
}
}
// 3. 判环:如果还有节点入度不为0,说明有环
for(int i : in)
if(i)
return false;
return true;
}
};
int main()
{
Solution sol;
// boolalpha:让cout输出 true/false,而不是 1/0
cout << boolalpha;
int n1 = 2;
vector<vector<int>> pre1 = {{1, 0}};
cout << "测试用例1(无环):" << sol.canFinish(n1, pre1) << endl;
int n2 = 2;
vector<vector<int>> pre2 = {{1, 0}, {0, 1}};
cout << "测试用例2(有环):" << sol.canFinish(n2, pre2) << endl;
int n3 = 6;
vector<vector<int>> pre3 = {{0,5},{2,5},{0,4},{1,4},{3,2},{1,3}};
cout << "测试用例3(复杂无环):" << sol.canFinish(n3, pre3) << endl;
return 0;
}

15.课程表||(OJ题)--BFS解决拓扑排序

算法思路:
和上⼀题⼀样,就不过多赘述了~
核心代码
cpp
class Solution
{
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites)
{
// 1. 准备工作
vector<vector<int>> edges(numCourses); // 邻接表存储图
vector<int> in(numCourses); // 存储每一个点的入度
// 2. 建图
for(auto& p : prerequisites)
{
int a = p[0], b = p[1]; // b -> a
edges[b].push_back(a);
in[a]++;
}
// 3. 拓扑排序
vector<int> ret; // 统计排序后的结果
queue<int> q;
for(int i = 0; i < numCourses; i++)
{
if(in[i] == 0) q.push(i);
}
while(q.size())
{
int t = q.front(); q.pop();
ret.push_back(t);
for(int a : edges[t])
{
in[a]--;
if(in[a] == 0) q.push(a);
}
}
if(ret.size() == numCourses) return ret;
else return {};
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class Solution
{
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites)
{
//1.准备工作
vector<vector<int>> edges(numCourses); //邻接表存储图
vector<int> in(numCourses); //存储每一个点的入度
//2.建图
for(auto& p : prerequisites)
{
int a = p[0], b = p[1]; //学习a需要先学b,建边 b -> a
edges[b].push_back(a);
in[a]++;
}
//3.拓扑排序(BFS)
vector<int> ret; //存储最终的课程学习顺序
queue<int> q;
//初始化:将所有入度为0的节点加入队列
for(int i = 0; i < numCourses; i++)
{
if(in[i] == 0)
q.push(i);
}
//BFS遍历
while(q.size())
{
int t = q.front();
q.pop();
ret.push_back(t);
// 遍历后继节点,入度减1
for(int a : edges[t])
{
in[a]--;
if(in[a] == 0)
q.push(a);
}
}
//4.判断是否能完成所有课程
if(ret.size() == numCourses)
return ret;
else
return {}; //有环,返回空数组
}
};
void printResult(vector<int>& res)
{
if(res.empty())
{
cout << "无法完成所有课程,存在环!" << endl;
return;
}
cout << "课程学习顺序:";
for(int x : res)
{
cout << x << " ";
}
cout << endl;
}
int main()
{
Solution sol;
int n1 = 2;
vector<vector<int>> pre1 = {{1, 0}};
vector<int> res1 = sol.findOrder(n1, pre1);
cout << "测试用例1:";
printResult(res1);
int n2 = 4;
vector<vector<int>> pre2 = {{1,0},{2,0},{3,1},{3,2}};
vector<int> res2 = sol.findOrder(n2, pre2);
cout << "测试用例2:";
printResult(res2);
int n3 = 2;
vector<vector<int>> pre3 = {{1,0},{0,1}};
vector<int> res3 = sol.findOrder(n3, pre3);
cout << "测试用例3:";
printResult(res3);
return 0;
}

16.火星词典(OJ题)--BFS解决拓扑排序

算法思路:解法(可以⽤拓扑排序解决)
如何搜集信息(如何建图):
(1)两层 for 循环枚举出所有的两个字符串的组合;
(2)然后利⽤指针,根据字典序规则找出信息.
(3)成员变量:存图、入度、非法标记
(4)主函数 alienOrder:初始化 → 建图 → BFS 拓扑排序 → 判环返回结果
(5)辅助函数 add:比较两个单词,建立字母依赖关系(核心建图逻辑)
1. 成员变量
edges:哈希表 + 集合,key = 前驱字母,value = 后继字母集合,自动去重(避免重复加边)
in:key = 字母,value = 入度(依赖的前置字母数量)
cheak:标记非法输入(比如 abc 排在 ab 前面,违法字典序规则)
2. 主函数:alienOrder
- 步骤 1:初始化入度表(所有字符入度 = 0)
作用:保证所有出现过的字母都被记录,不漏掉任何字符. - 步骤 2:构建有向图
两两比较单词,调用 add 函数建立字母的依赖关系;
一旦发现非法输入,直接返回空串. - 步骤 3:BFS 拓扑排序
这就是Kahn 算法:
入度为 0 → 无依赖 → 优先处理
处理完一个字母,它的后继字母入度 - 1
入度为0就入队,保证顺序合法 - 步骤 4:判环 + 返回结果
拓扑排序的核心判环规则:
结果长度 = 总字符数 → 无环;否则有环,无法排序.
3. 辅助函数:add
作用:比较前一个单词s1和后一个单词 s2,建立字母依赖关系.
核心代码
cpp
class Solution
{
unordered_map<char, unordered_set<char>> edges; // 邻接表来存储图
unordered_map<char, int> in; // 统计入度
bool cheak; // 处理边界情况
public:
string alienOrder(vector<string>& words)
{
// 1. 建图 + 初始化入度哈希表
for(auto& s : words)
{
for(auto ch : s)
{
in[ch] = 0;
}
}
int n = words.size();
for(int i = 0; i < n; i++)
{
for(int j = i + 1; j < n; j++)
{
add(words[i], words[j]);
if(cheak) return "";
}
}
// 2. 拓扑排序
queue<char> q;
for(auto& [a, b] : in)
{
if(b == 0) q.push(a);
}
string ret;
while(q.size())
{
char t = q.front(); q.pop();
ret += t;
for(char ch : edges[t])
{
if(--in[ch] == 0) q.push(ch);
}
}
// 3. 判断
for(auto& [a, b] : in)
if(b != 0) return "";
return ret;
}
void add(string& s1, string& s2)
{
int n = min(s1.size(), s2.size());
int i = 0;
for( ; i < n; i++)
{
if(s1[i] != s2[i])
{
char a = s1[i], b = s2[i]; // a -> b
if(!edges.count(a) || !edges[a].count(b))
{
edges[a].insert(b);
in[b]++;
}
break;
}
}
if(i == s2.size() && i < s1.size()) cheak = true;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <string>
using namespace std;
class Solution
{
unordered_map<char, unordered_set<char>> edges; // 邻接表存储图
unordered_map<char, int> in; // 统计入度
bool check; // 修正笔误:cheak -> check,标记非法输入
public:
string alienOrder(vector<string>& words)
{
check = false; // 初始化非法标记
// 1. 初始化入度哈希表:所有出现的字符入度=0
for(auto& s : words)
{
for(auto ch : s)
{
in[ch] = 0;
}
}
int n = words.size();
// 两两比较单词,构建有向图
for(int i = 0; i < n; i++)
{
for(int j = i + 1; j < n; j++)
{
add(words[i], words[j]);
if(check) return ""; // 非法输入,直接返回空
}
}
// 2. BFS 拓扑排序
queue<char> q;
// 入度为0的节点入队
for(auto& p : in)
{
if(p.second == 0)
q.push(p.first);
}
string ret;
while(!q.empty())
{
char t = q.front();
q.pop();
ret += t;
// 遍历后继节点,入度-1
for(char ch : edges[t])
{
if(--in[ch] == 0)
q.push(ch);
}
}
// 3. 判断是否有环
for(auto& p : in)
{
if(p.second != 0)
return "";
}
return ret;
}
void add(string& s1, string& s2)
{
int len = min(s1.size(), s2.size());
int i = 0;
for( ; i < len; i++)
{
// 找到第一个不同的字符,建立依赖 a -> b
if(s1[i] != s2[i])
{
char a = s1[i], b = s2[i];
// 避免重复添加边
if(!edges.count(a) || !edges[a].count(b))
{
edges[a].insert(b);
in[b]++;
}
break;
}
}
// 非法情况:s2是s1的前缀,且s1更长(如 abc 在 ab 前)
if(i == s2.size() && i < s1.size())
check = true;
}
};
void printRes(string& s)
{
if(s.empty())
cout << "无合法字母顺序(存在环/非法输入)" << endl;
else
cout << "火星文字母顺序:" << s << endl;
}
int main()
{
Solution sol;
vector<string> words1 = {"wrt","wrf","er","ett","rftt"};
string res1 = sol.alienOrder(words1);
cout << "测试用例1:";
printRes(res1);
vector<string> words2 = {"z","x"};
string res2 = sol.alienOrder(words2);
cout << "测试用例2:";
printRes(res2);
vector<string> words3 = {"z","x","z"};
string res3 = sol.alienOrder(words3);
cout << "测试用例3:";
printRes(res3);
vector<string> words4 = {"abc","ab"};
string res4 = sol.alienOrder(words4);
cout << "测试用例4:";
printRes(res4);
return 0;
}


🚀真正的勇者不是流泪的人,而是含泪奔跑的人!
敬请期待下一篇文章内容:优选算法内容的内容到这里就圆满结束啦!小编开始继续学习另外的算法领域,不断提高自己的算法能力,喜欢小编的可以继续跟着我的步伐一起继续前行!
每日心灵鸡汤:放下即是释怀,前行皆不后悔!
释怀是每个人一生的必修课我一直觉得,后悔就是在欺负以前的自己,人不可能在任何一步都正确,我不想回头看,也不想批判当时的自己,没什么好抱怨的,我大大方方的为我的认知买单,也没什么好自责的,如果重来一次,以我当时的阅历和心智,还是会做出同样的选择,我们不能站在现在的高度,去批判当时的自己,你要知道人生没有白走的路,我始终相信,所有的遗憾和痛苦都是应该经历的,人生永远没有正确的选择,要让选择变得正确.山有顶峰,湖有彼岸,在人生漫漫长途中,万物皆有回转.所以山确实有顶峰,但路是没有终点的,当我们不再与过去的事物纠缠,脚下的土地会生长出新的可能,最好的人生,从来不是修正旧剧本,而是带着所有故事,认真写好当下每一页.不要后悔对任何一个人好,哪怕是看错人,哪怕是被辜负,哪怕是撞南墙,因为你对她好,不代表她有多好,只是因为你很好.买了就不要再去比价格,吃了就不要后悔,爱了就不要猜疑,散了也不要诋毁,所有的一切,只不过是在为自己的选择买单.有心者有所累,无心者无所谓,情出自愿事过无悔,不负遇见,不谈亏欠.很多时候回过头来看看,让自己苦不堪言的纠结焦虑,其实都是自己和自己在较劲.
