【广度优先搜索】N叉树的层序遍历 | 腐烂的橘子 | 单词接龙 | 最小基因变化 | 打开转盘锁

news2024/11/29 14:54:38

🌠作者:@阿亮joy.
🎆专栏:《数据结构与算法要啸着学》
🎇座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述


目录

    • 👉广度优先搜索👈
    • 👉N叉树的层序遍历👈
    • 👉腐烂的橘子👈
    • 👉单词接龙👈
    • 👉最小基因变化👈
    • 👉打开转盘锁👈
    • 👉总结👈

👉广度优先搜索👈

广度优先搜索算法(BFS)是通过队列来实现搜索的,其通常用于解决最短路径、最小步数等问题,深度优先搜索往往无法解决这些问题。

广度优先搜索模板

BFS()
{
	1. 建立起始步骤,队列初始化
	2. 遍历队列中的每一种可能,whlie(队列不为空)
	{
		通过队头元素带出下一步的所有可能,并且依次入队
		{
			判断当前情况是否达成目标:按照目标要求处理逻辑
		} 
			继续遍历队列中的剩余情况
		}
}

👉N叉树的层序遍历👈

给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。

树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。

在这里插入图片描述

思路:用当前层的节点将下一层的节点带入队列,当前层的节点全部出完队列,下一层的节点就全部入队列了,队列的大小就是下一层节点的个数。

/*
// Definition for a Node.
class Node 
{
public:
    int val;
    vector<Node*> children;
};
*/

class Solution 
{
public:
    vector<vector<int>> levelOrder(Node* root) 
    {
        vector<vector<int>> vv;
        queue<Node*> q;
        // 根节点不为空才入队列
        if(root)
            q.push(root);

        while(!q.empty())
        {
            // 获取当前层节点的个数
            int levelSize = q.size();
            vector<int> v;
            // 收集一层的节点
            while(levelSize--)
            {
                Node* front = q.front();
                q.pop();
                v.push_back(front->val);
                // 下一层节点入队列
                for(auto& e : front->children)
                {
                    q.push(e);
                }
            }
            // 将一层节点存储的值放入结果中
            vv.push_back(v);
        }

        return vv;
    }
};

在这里插入图片描述

👉腐烂的橘子👈

在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:

  • 值 0 代表空单元格;
  • 值 1 代表新鲜橘子;
  • 值 2 代表腐烂的橘子。

每分钟,腐烂的橘子周围 4 个方向上相邻的新鲜橘子都会腐烂。

返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。

在这里插入图片描述

思路:本题可以先找到所有的腐烂橘子,入队列,用第一批带出新一批腐烂的橘子,每一批橘子都会在一分钟之内腐烂,所以此题可以转化为求广度优先搜索执行的最大循环次数。这里的 step 的更新需要有一个标记,只有新的腐烂的橘子加入,step 才能自加。最后广度优先搜索执行完之后,说明所有可以被腐烂的都完成了。再去遍历 grid,如何还有值为 1 的,说明没有办法完全腐烂,返回 -1,如果没有,则返回 step(注:step 是直到单元格中没有新鲜橘子为止所必须经过的最小分钟数)。

class Solution 
{
public:
    int orangesRotting(vector<vector<int>>& grid) 
    {
        // 队列是用来存储腐烂橘子的坐标的
        queue<pair<int, int>> q;
        int row = grid.size();
        int col = grid[0].size();

        // 腐烂的橘入队列
        for(int i = 0; i < row; ++i)
        {
            for(int j = 0; j < col; ++j)
            {
                if(grid[i][j] == 2)
                    q.push(make_pair(i, j));
            }
        }

        // 可以蔓延的方向
        int nextPosition[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        int step = 0;   // 最小分钟数
        while(!q.empty())
        {
            int size = q.size();
            bool flag = false;  // 判断腐烂橘子周围有没有新鲜橘子
            // 用当前这一批已经腐烂的橘子带出下一批要腐烂的橘子
            // 故要遍历队列中的所有位置
            while(size--)
            {
                auto curPos = q.front();
                q.pop();
                // 查找腐烂橘子周围有没有新鲜橘子
                for(int i = 0; i < 4; ++i)
                {
                    int newX = curPos.first + nextPosition[i][0];
                    int newY = curPos.second + nextPosition[i][1];
                    // 如果位置越界或者是空格,或者已经是腐烂的位置,则跳过
                    if(newX < 0 || newX >= row
                    || newY < 0 || newY >= col
                    || grid[newX][newY] != 1)
                        continue;
                    // 说明腐烂橘子的周围有新鲜橘子
                    flag = true;    
                    grid[newX][newY] = 2;
                    // 腐烂橘子入队列
                    q.push(make_pair(newX, newY));
                }
            }
            // 腐烂橘子周围有新鲜橘子才能加加step
            if(flag)
                ++step;
        }

        // 查看还有没有新鲜的橘子,如果有,返回-1
        // 如果没有,返回step
        for(int i = 0; i < row; ++i)
        {
            for(int j = 0; j < col; ++j)
            {
                if(grid[i][j] == 1)
                    return -1;
            }
        }

        return step;
    }
};

在这里插入图片描述

👉单词接龙👈

这里是引用

思路:

  1. 通过广度优先搜索,首先用 beginWord 带出转换一个字母之后所有可能的结果。
  2. 每一步都要把队列中上一步添加的所有单词转换一遍,最短的转换肯定在这些单词当中, 所有这些词的转换只能算一次转换,因为都是上一步转换出来的。这里对于每个单词的每个位置都可以用 25 个字母进行转换,所以一个单词一次转换的可能有:单词的长度 * 25。
  3. 把转换成功的新词入队,进行下一步的转换。
  4. 最后整个转换的长度就和广度优先搜索执行的次数相同。

在这里插入图片描述

class Solution 
{
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) 
    {
        // 用哈希表来查询单词是否存在
        unordered_set<string> wordDict(wordList.begin(), wordList.end());
        // 在visited中的单词表示已经搜索过了,不能再搜索了
        unordered_set<string> visited;
        visited.insert(beginWord);
        // 初始化队列
        queue<string> q;
        q.push(beginWord);

        int step = 1;
        while(!q.empty())
        {
            int size = q.size();
            // 每一步都要把队列中上一步添加的所有单词转换一遍
            // 最短的转换肯定在这些单词当中,所有这些词的转换只能算一次转换
            // 因为都是上一步转换出来的
            while(size--)
            {
                string curStr = q.front();
                q.pop();     
                // 存在转换序列
                if(curStr == endWord)
                    return step;

                // 尝试转换当前单词的每一个位置
                for(int i = 0; i < curStr.size(); ++i)
                {
                    string newStr = curStr;
                    // 每个位置上的字母都转换一次
                    for(char ch = 'a'; ch <= 'z'; ++ch)
                    {
                        newStr[i] = ch;

                        // 判断新的单词是否在词典中且没有搜索过
                        if(wordDict.count(newStr) == 1 
                        && visited.count(newStr) == 0)
                        {
                            q.push(newStr);
                            // 标记已经搜索过
                            visited.insert(newStr);
                        }
                    }
                }        
            }
            ++step;
        }

        return 0;   // 不存在转换序列
    }
};

在这里插入图片描述

👉最小基因变化👈

这里是引用

注:最小基因变化的思路和单词接龙的思路是一样的,可参考单词接龙的思路。

class Solution 
{
public:
    int minMutation(string startGene, string endGene, vector<string>& bank) 
    {
        // 用哈希表作为基因库,查询效率更高
        unordered_set<string> HashBank(bank.begin(), bank.end());
        // 已经搜索过的基因序列
        unordered_set<string> visited;
        visited.insert(startGene);
        // 初始化队列
        queue<string> q;
        q.push(startGene); 
        // 基因
        char arr[4] = {'A', 'C', 'G', 'T'};

        int step = 0;
        while(!q.empty())
        {
            int size = q.size();
            // 队列中的基因序列都是一步转换得来的,所以最小的基因变化
            // 肯定在队列中
            while(size--)
            {
                string curStr = q.front();
                q.pop();

                // 能将startGene转化为endGene
                if(curStr == endGene)
                    return step;

                // 将每个位置上的基因都尝试变一变
                for(int i = 0; i < 8; ++i)
                {
                    string newStr = curStr;
                    // 基因有四种变法
                    for(int j = 0; j < 4; j++)
                    {
                        newStr[i] = arr[j];

                        // 有效且没有被搜索过的基因序列才能够入队列
                        if(HashBank.count(newStr) == 1
                        && visited.count(newStr) == 0)
                        {
                            q.push(newStr);
                            // 标记该基因序列已经搜索过了
                            visited.insert(newStr);
                        }
                    }
                }
            }
            ++step;
        }
        return -1;
    }
};

在这里插入图片描述

👉打开转盘锁👈

打开转盘锁的思路还是和单词接龙的思路一样,可以参照单词接龙的思路。需要注意的是,本题的密码为 4 位密码,每位密码可以通过拨动一次进行改变,注意这里的数的回环以及拨动的方向。如果当前是 9 时,那么逆时针转就会变成 0,;如果当前是 0 时,那么顺时针转就会变成 9。

class Solution 
{
public:
    int openLock(vector<string>& deadends, string target) 
    {
        // 将死亡数字加入到哈希表中,提供查询效率
        unordered_set<string> deadendsSet(deadends.begin(), deadends.end());
        //如果"0000"也是死亡数字,则永远到达不了
        if(deadendsSet.count("0000"))
            return -1;

        // 已经搜索过的字符串不需要再次搜索
        unordered_set<string> visited;
        visited.insert("0000");
        // 初始化队列
        queue<string> q;
        q.push("0000");

        int step = 0;
        while(!q.empty())
        {
            int size = q.size();
            // 从上一步转换之后的字符串都需要进行验证和转换
            // 并且只算做一次转换,类似于层序遍历,转换的步数和层相同
            // 同一层的元素都是经过一步转换得到的
            while(size--)
            {
                string curStr = q.front();
                q.pop();

                if(curStr == target)
                    return step;

                for(int i = 0; i < 4; ++i)
                {
                    // newStr1是顺时针旋转得到的密码
                    // newStr2是逆时针旋转得到的密码
                    string newStr1 = curStr;
                    string newStr2 = curStr;
                    // 顺时针
                    newStr1[i] = newStr1[i] == '0' ? '9' : newStr1[i] - 1;
                    if(deadendsSet.count(newStr1) == 0
                    && visited.count(newStr1) == 0)
                    {
                        q.push(newStr1);
                        visited.insert(newStr1);
                    }
                    // 逆时针旋转
                    newStr2[i] = newStr2[i] == '9' ? '0' : newStr2[i] + 1; 
                    if(deadendsSet.count(newStr2) == 0
                    && visited.count(newStr2) == 0)
                    {
                        q.push(newStr2);
                        visited.insert(newStr2);
                    }
                }
            }
            ++step;
        }
        return -1;
    }
};

在这里插入图片描述

👉总结👈

本篇博客主要讲解了主要讲解了广度优先搜索的模型以及几道广度优先搜索的题目:N 叉树的层序遍历、腐烂的橘子、单词接龙、最小基因变化、打开转盘锁等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

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

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

相关文章

jenkins打包构建springboot项目为docker镜像并上传nexus私服

前提&#xff1a;jenkins,docker,nexus都已经搭建完毕 一. 开启docker远程访问 1.修改docker.service文件 Docker 安装成功之后&#xff0c;首先需要修改 Docker 配置来开启允许远程访问 Docker 的功能。     文件位置&#xff1a;/lib/systemd/system/docker.service    …

JVM(Java虚拟机)详解(JVM 内存模型、堆、GC、直接内存、性能调优)

JVM&#xff08;Java虚拟机&#xff09; JVM 内存模型 结构图 jdk1.8 结构图&#xff08;极简&#xff09; jdk1.8 结构图&#xff08;简单&#xff09; JVM&#xff08;Java虚拟机&#xff09;&#xff1a; 是一个抽象的计算模型。如同一台真实的机器&#xff0c;它有自己…

使用比console.log更优质的前端调试方案

程序调试是程序开发必不可少的一环&#xff0c;刚开始接触前端调试时&#xff0c;最常用的就是 console.log 打印变量进行调试&#xff0c;本文会分享一些笔者学习到的前端调试方法&#xff0c;减少对 console.log 调试方式的依赖&#xff0c;选择更优质的前端调试方案。 本文中…

Android开发进阶——RxJava核心架构分析

简介 RxJava是对响应式扩展&#xff08; Reactive Extensions&#xff0c;称之为 ReactiveX &#xff09;规范的Java 实现&#xff0c;该规范还有其他语言实现&#xff1a;RxJS、Rx.Net、RxScala、RxSwift等等&#xff08;也即&#xff0c;ReactiveX 定义了规范&#xff0c;其他…

pip安装时报错 ascii‘ codec can‘t decode byte 0xe2 in position...

在使用pip安装包的时候报错ascii’ codec can’t decode byte 0xe2 in position… 报错信息 UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xe2 in position 1429: ordinal not in range(128) 以前好像见过类似的情况&#xff0c;所以搜了一下怎么修改python默认…

聊聊ChatGPT

最近ChatGPT火出圈。 在过去三个月里&#xff0c;眼见着它的热度火箭一般蹿升&#xff0c;据瑞银上周三发布的报告显示&#xff0c;GPT已经超过了Tiktok&#xff0c;成为人类有史以来最快突破1亿月活跃用户的互联网产品。Tiktok当初用了9个月&#xff0c;而GPT只用了2个月。 …

Spring使用了哪些设计模式?

目录Spring中涉及的设计模式总结1.简单工厂(非23种设计模式中的一种)2.工厂方法3.单例模式4.适配器模式5.装饰器模式6.代理模式7.观察者模式8.策略模式9.模版方法模式Spring中涉及的设计模式总结 1.简单工厂(非23种设计模式中的一种) 实现方式&#xff1a; BeanFactory。Spri…

go-grpc的使用和学习

文章目录基础知识&#xff1a;操作流程安装proto文件配置grpc&#xff1a; 正常客户端发送数据(以字节流的方式)&#xff0c;服务器接受并解析&#xff0c;根据约定知道要执行什么&#xff0c;然后把结果返回给客户 rpc将上述过程封装&#xff0c;使其操作更加优化&#xff0c;…

Vue 3 中的极致防抖/节流(含常见方式防抖/节流)

各位朋友你们好呀。今天是立春&#xff0c;明天就是正月十五元宵节了&#xff0c;这种立春 元宵相隔的时候&#xff0c;可是很难遇到的&#xff0c;百年中就只有几次。在这提前祝大家元宵快乐。 今天给大家带来的是Vue 3 中的极致防抖/节流&#xff08;含常见方式防抖/节流&a…

ChatGPT给程序员人手一个,这很朋克

目录ChatGPT、程序员、朋克为什么程序员需要ChatGPT&#xff0c;为什么这很朋克总结ChatGPT、程序员、朋克 本文由ChatGPT编写。 ChatGPT是由OpenAI开发的大型语言模型。它的核心功能是生成人类语言文本&#xff0c;因此有多种应用场景&#xff0c;如文本生成、对话生成、文本…

「VUE架构」Vue2与Vue3的区别

文章目录前言一、性能比Vue2快1.2~2倍1.1 diff算法优化1.2 事件侦听缓存1.3 减少创建组件实例的开销二、 按需编译&#xff0c;体积比Vue2更小三、 Compostion API四、 支持TS五、 自定义渲染API六、更先进的组件七、 更快的开发体验前言 VUE是一套用于构建用户界面的渐进式框…

Nginx常用功能举例解析

Nginx提供的基本功能服务从大体上归纳为"基本HTTP服务"、“高级HTTP服务”和"邮件服务"等三大类。基本HTTP服务Nginx可以提供基本HTTP服务&#xff0c;可以作为HTTP代理服务器和反向代理服务器&#xff0c;支持通过缓存加速访问&#xff0c;可以完成简单的…

【FPGA】Verilog:组合逻辑电路应用 | 数码管 | 8421BCD编码 | 转换七段数码管段码

前言&#xff1a;本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载 示例&#xff1a;数码管的使用 功能特性&#xff1a; 采用 Xilinx Artix-7 XC7A35T芯片 配置方式&#xff1a;USB-JTAG/SPI Flash 高达100MHz 的内部时钟速度 存储器&#xff1a;2M…

Vue-VueRouter

前言 Vue Router 是 Vue.js (opens new window)官方的路由管理器。它和 Vue.js 的核心深度集成&#xff0c;让构建单页面应用变得易如反掌。包含的功能有&#xff1a; 嵌套的路由/视图表模块化的、基于组件的路由配置路由参数、查询、通配符基于 Vue.js 过渡系统的视图过渡效果…

SPSS聚类分析(含k-均值聚类,系统聚类和二阶聚类)

本篇博客主要是根据1、聚类的基本知识点_哔哩哔哩_bilibili系列视频进行的学习记录一、SPSS聚类分析的基本知识点1、什么是聚类分析?聚类分析(Cluster analysis)又叫做群集分析,通过一些属性将对象或变量分成不同的组别&#xff0c;在同一类下的对象或变量在这些属性上具有一些…

最全面的SpringBoot教程(四)——数据库连接

前言 本文为 最全面的SpringBoot教程&#xff08;四&#xff09;——数据库连接 相关知识&#xff0c;下边将对JDBC连接配置&#xff0c;与使用Druid数据源&#xff0c;从添加依赖到修改配置项再到测试进行详尽介绍~ &#x1f4cc;博主主页&#xff1a;小新要变强 的主页 &…

C语言深度解剖-关键字(5)

目录 if else 组合 if else 的基本用法 注释 深入理解bool float(double)与浮点数的比较 写在最后&#xff1a; if else 组合 if else 的基本用法 #include <stdio.h>int main() {int flag 1;if (flag 1){printf("flag %d\n", flag);}else if (flag…

crmeb pro多门店版二次开发

重启一下swool 新创建的数据库表 ALTER TABLE eb_store_cart ADD price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 单独改价 AFTER status;ALTER TABLE eb_store_order ADD car_no VARCHAR(255) NOT NULL DEFAULT COMMENT 车牌号 AFTER erp_order_id, ADD frame_no VARCHA…

2023软考系统集成项目管理工程师易混淆知识点~(7)

将2023上半年软考《系统集成项目管理工程师》易混淆知识点&#xff0c;分享给大家&#xff0c;快来跟着一起打卡学习吧&#xff01;概念辨析 1:项目、运营概念:(1)项目:项目是为达到特定目的&#xff0c;使用一定资源&#xff0c;在确定的期间内&#xff0c;为特定发起人而提供…

Linux perf的使用(一)

文章目录前言一、perf简介二、perf子命令简介三、perf工作模式3.1 计数3.2 采样参考资料前言 系统级性能优化通常包括两个阶段&#xff1a;性能剖析&#xff08;performance profiling&#xff09;和代码优化。 &#xff08;1&#xff09;性能剖析的目标是寻找性能瓶颈&#x…