【优选算法】(实战攻坚BFS之FloodFill、最短路径问题、多源BFS以及解决拓扑排序)


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

在图论与算法实战领域,广度优先搜索(BFS)始终是不可或缺的核心工具,其"逐层遍历、先广后深"的核心特性,使其在处理连通性、路径查找、依赖排序等问题时具备天然优势,成为面试考核与工程实践中的高频考点.不同于基础理论的浅层认知,实际应用中的BFS往往需要结合场景进行灵活变形,而FloodFill、最短路径、多源BFS及拓扑排序,正是BFS实战中最具代表性、也最易让人陷入瓶颈的四大核心场景.FloodFill算法作为BFS的经典应用,以"种子扩散"为核心,广泛应用于图像处理、游戏开发等领域,其核心在于邻域判定与边界控制的精准把握;无权图中的最短路径问题,是BFS最基础的实战应用,凭借其层级遍历的特性,能够确保首次访问目标节点时的路径为最短路径,无需额外的路径优化;多源BFS则是单源BFS的扩展,通过多个起点同时遍历,高效解决多源最短路径、范围覆盖等问题,相比暴力解法大幅提升效率,适用于疫情扩散、01矩阵等场景;而基于BFS的拓扑排序(Kahn算法),则为有向无环图(DAG)的依赖排序提供了高效解决方案,在任务调度、课程安排、编译顺序等实际场景中发挥着重要作用.本文聚焦BFS实战攻坚,摒弃单纯的理论堆砌,以"场景拆解+核心逻辑+实战导向"为核心,逐一剖析FloodFill、最短路径、多源BFS及拓扑排序的解题思路、实现技巧与边界处理方法.通过梳理四大场景的共性规律与差异化特点,帮助学习者打通BFS从理论到实战的壁垒,掌握不同场景下的算法选型与优化策略,提升解决复杂算法问题的能力,为面试攻坚与工程实践奠定坚实基础.废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!

目录

1.BFS思想背景以及FloodFill算法简介

BFS思想背景

  1. 历史起源与发展
  • 早期雏形: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年才正式出版.
  1. 核心思想与特点
  • 基本定义 :广度优先搜索(Breadth-First Search, BFS)是一种逐层递进、地毯式的图遍历算法,从起始节点开始,先访问所有直接相邻的节点(第一层),再访问这些节点的所有未访问邻居(第二层),以此类推,直到遍历完所有可达节点
  • 核心数据结构队列(Queue),遵循"先进先出(FIFO)"原则,确保节点按访问顺序处理
  • 关键特性
    • 最短路径保证 :在无权图中,BFS能找到从起点到终点的最短路径(边数最少)
    • 完备性:若目标节点存在,BFS一定能找到它
    • 时间复杂度:O(V+E),其中V是节点数,E是边数
    • 空间复杂度:最坏情况下为O(V),需要存储所有节点
  1. 思想来源

BFS的核心思想源于人类的直觉式搜索行为,如:

  • 向水中投入石子,水波层层扩散的物理过程
  • 迷宫中从起点出发,依次探索所有相邻通道,确保不遗漏任何路径
  • 社交网络中,查找与某人相距k度的所有联系人

FloodFill算法简介

  1. 定义与核心思想

    FloodFill(泛洪填充/洪水填充)是一种区域填充算法,核心思想源于现实生活中的"洪水泛滥"过程:从一个种子点(起始点)出发,按照特定规则(如颜色相同、数值相等)向相邻区域扩散,直至遇到边界条件(如不同颜色、障碍物等)为止.其本质是在二维网格(如图像像素阵列、棋盘、地图)中识别并标记所有与种子点连通的区域.

  2. 连通性定义

FloodFill算法有两种常用的连通性判断标准:

连通类型 包含方向 适用场景
四邻域 上、下、左、右 图像处理、简单游戏地图
八邻域 上、下、左、右、四个对角线 地理信息系统、复杂连通区域分析
  1. 实现方式
    FloodFill算法可通过两种经典搜索算法实现,各有优劣:
实现方式 核心数据结构 特点 适用场景
DFS(深度优先搜索) 递归调用栈/显式栈 代码简洁,"一条路走到黑",但容易栈溢出(大规模区域) 小规模图像,学习与教学目的
BFS(广度优先搜索) 队列 迭代实现,空间可控,"涟漪扩散",工程实践首选 大规模图像,避免栈溢出问题

BFS实现FloodFill的基本步骤:

  1. 检查种子点是否在有效范围内,目标颜色与替换颜色是否不同

  2. 创建队列,将种子点入队,并标记为已访问

  3. 当队列不为空时:

    • 出队一个节点
    • 将其颜色改为目标颜色
    • 检查四个(或八个)相邻节点
    • 若相邻节点颜色为原始颜色且未访问,入队并标记为已访问
  4. 队列空时,算法结束

  5. 应用场景

    FloodFill算法广泛应用于多个领域:

  • 图像处理:绘图软件中的"油漆桶工具"、图像分割、去除噪点
  • 游戏开发:扫雷游戏中空白区域的自动扩展、地图区域标记、角色移动范围计算
  • 地理信息系统(GIS):洪水灾害模拟、区域边界提取、地形分析
  • 医学图像分析:肿瘤区域识别、器官分割、血管网络提取
  • 其他领域:迷宫求解、电路板布线、连通区域统计

BFS与FloodFill的关系

  1. 本质联系 :FloodFill是BFS/DFS算法在二维网格上的具体应用,专注于区域填充问题
  2. 区别
    • BFS是通用图遍历算法,适用于任何图结构,目标是遍历所有可达节点
    • FloodFill是专用区域填充算法,主要用于二维网格,目标是识别并填充连通区域
  3. 实现选择 :在实际开发中,优先选择BFS迭代实现FloodFill,避免DFS递归的栈溢出风险,尤其处理大规模图像时

2.图像渲染(OJ题)--BFS解决FloodFill算法


算法思路:

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

核心逻辑(FloodFill + BFS 本质)

  1. 算法思想
    起始坐标为种子 ,用 BFS 逐层向外扩散 ,把所有和起始点连通、同色的格子,全部染成目标颜色.
  2. BFS 优势
    逐层遍历,天然适合区域填充、连通域查找,是 FloodFill 最直观的实现方式.
  3. 关键细节
    • 提前判重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精准统计连通域大小.
  1. 边界处理(BFS网格通用规则)
  • 坐标必须在网格范围内,防止数组越界;
  • 仅遍历陆地1 ,跳过水域0;
  • 仅处理未访问的节点,保证每个格子只遍历一次.
  1. 执行流程
  • 遍历网格所有单元格;
  • 找到未访问的陆地 → 启动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求最短路):

利⽤层序遍历来解决迷宫问题,是最经典的做法.

我们可以从起点开始层序遍历,并且在遍历的过程中记录当前遍历的层数.这样就能在找到出⼝的时候,得到起点到出⼝的最短距离.
🔥关键实战技巧

  1. 层级遍历固定模板
cpp 复制代码
   int sz = q.size();
   for(int i=0; i<sz; i++){ ... }
  1. 原地出口

    直接判断坐标是否在迷宫边界,无需额外存储出口位置,简化:

  2. 提前返回优化

    BFS首次到达出口即为最短路径,直接返回结果,无需冗余遍历,效率拉满"

  3. 通用骨架复用

    四方向数组、边界校验、队列遍历,完全复用FloodFill的BFS基础模板,体现一套骨架适配多场景的实战规律.

  4. 边界处理(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即可.
🔥实战核心逻辑拆解

  1. 核心思想:状态转移 + 层级BFS
  • 迷宫 :坐标 (x,y) 是状态,上下左右移动是转移;
  • 基因变异:字符串是状态,单字符修改是转移;
  • 共性无权最短路径 → 层级BFS → 首次到达即最短.
  1. 实战优化技巧
  • 哈希集合加速
    unordered_set 存储基因库和访问记录,查找效率O(1),远优于vector遍历,是字符串BFS的标配优化;
  • 层级遍历模板
    固定队列大小 sz,严格按层遍历,保证步数统计精准(BFS最短路径灵魂);
  • 原地状态修改
    备份字符串逐位变异,无额外空间开销,代码简洁高效;
  • 提前剪枝返回
    找到目标立即返回,无需冗余遍历,时间复杂度最优.
  1. 边界处理(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解决最短路径问题


核心逻辑拆解

  1. 层级BFS:最短路径的灵魂
    这是本题与FloodFill 的核心区别,也是所有最短路径题的唯一解法:
  • FloodFill:仅需扩散遍历连通域,无步数要求;
  • 最短路径 :必须按层遍历 ,每一层对应一次单词转换 ,第一次到达终点的层数就是最短步数.
  1. 状态转移:BFS从网格到字符串的迁移
  • 网格BFS:状态 = 坐标 (x,y),转移 = 上下左右移动;
  • 单词接龙:状态 = 字符串,转移 = 单字符替换;
  • 共性规律 :BFS 只关心状态扩散,不关心状态的具体形式.
  1. 实战优化技巧
  • 哈希集合加速
    unordered_set 实现 O(1) 查找,是字符串BFS的标配优化,避免超时;
  • 层级遍历固定模板
    sz = q.size() 严格控制每层遍历范围,保证步数统计100%精准;
  • 提前剪枝返回
    找到目标立即返回,无冗余遍历,时间复杂度最优;
  • 原地状态修改
    备份字符串逐位替换,无额外空间开销,代码极简.
  1. 边界处理(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 求出最短路即可.

核心实战拆解

  1. 场景拆解:从复杂问题到BFS基础场景

    本题是复合场景,拆解后完全回归BFS基础能力:

  2. 排序预处理:按规则确定遍历顺序;

  3. 单源最短路径:两点之间的最短距离 → 层级BFS;

  4. 路径累加:多次BFS结果求和,得到总最小步数.

  5. 核心逻辑:层级BFS

  • FloodFill:仅标记连通域,无步数要求;
  • 最短路径按层遍历 ,每一层对应一步,第一次到达终点的步数就是最小值.
  1. 实战技巧
  • 全局变量简化m/n/vis/dx/dy 全局化,省去反复传参;
  • 每次BFS重置访问数组memset 清空脏数据,避免错误;
  • 提前剪枝 :任意一段路径不通,直接返回 -1,无冗余计算;
  • 入队即标记:杜绝节点重复入队,避免超时/死循环.
  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 核心原理

  1. BFS 是层序遍历 (一层一层向外走),天然适合求最短路径
  2. 多源BFS 把所有起点当作第0层,一次性全部加入队列
  3. 同步扩散,保证每个位置只会被最近的起点最先到达,效率极高

时间优势

如果用单源BFS遍历N个起点:时间复杂度 O ( N × M ) O(N \times M) O(N×M)

多源BFS:只遍历一次地图,时间复杂度 O ( M ) O(M) O(M)(M是格子总数)

多源BFS 标准解题步骤

  1. 初始化队列 :遍历地图,把所有起点坐标入队,同时标记访问/记录距离
  2. 方向数组:上下左右(四方向)/八方向
  3. 层序BFS:队列循环,逐层扩散,更新距离/状态
  4. 结果处理:统计答案

核心代码

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 算法)

  1. 先做没有依赖的任务(入度=0)
  2. 做完后,解除它对后续任务的限制(后续任务入度-1)
  3. 重复执行,直到所有任务做完
  4. 若最后有任务没做完 → 图中有环 → 无法拓扑排序

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;
}


🚀真正的勇者不是流泪的人,而是含泪奔跑的人!


敬请期待下一篇文章内容:优选算法内容的内容到这里就圆满结束啦!小编开始继续学习另外的算法领域,不断提高自己的算法能力,喜欢小编的可以继续跟着我的步伐一起继续前行!


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

相关推荐
漠缠1 小时前
缠论核心公理:走势终完美
学习·程序人生
kishu_iOS&AI1 小时前
机器学习 —— 线性回归(2)
人工智能·python·算法·机器学习·线性回归
arvin_xiaoting1 小时前
OpenClaw学习总结_IV_认证与安全_3:Authorization与Policies详解
学习·安全
EnglishJun1 小时前
ARM嵌入式学习(二十)--- 杂项设备、Platfrom总线和设备树源文件(dts)
学习
知识分享小能手1 小时前
MongoDB入门学习教程,从入门到精通,MongoDB 安全完全指南(19)
学习·安全·mongodb
NULL指向我1 小时前
信号处理学习笔记6:ADC采样线性处理实测拟合
人工智能·算法·机器学习
汽车仪器仪表相关领域1 小时前
NHXJ-02汽车悬架检验台 实操型实战手册
人工智能·功能测试·测试工具·算法·安全·单元测试·可用性测试
源码之屋2 小时前
计算机毕业设计:Python天气数据采集与可视化分析平台 Django框架 线性回归 数据分析 大数据 机器学习 大模型 气象数据(建议收藏)✅
人工智能·python·深度学习·算法·django·线性回归·课程设计
我爱C编程2 小时前
【3.2】FFT/IFFT变换的数学原理概述与MATLAB仿真
算法·matlab·fpga·fft·ifft