LeetCode 面试经典 150_图的广度优先搜索_最小基因变化(93_433_C++_中等)(广度优先搜索(BFS))

LeetCode 面试经典 150_图的广度优先搜索_最小基因变化(93_433_C++_中等)

题目描述:

基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 'A'、'C'、'G' 和 'T' 之一。

假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。

  • 例如,"AACCGGTT" --> "AACCGGTA" 就是一次基因变化。

另有一个基因库 bank 记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。(变化后的基因必须位于基因库 bank 中)

给你两个基因序列 start 和 end ,以及一个基因库 bank ,请你找出并返回能够使 start 变化为 end 所需的最少变化次数。如果无法完成此基因变化,返回 -1 。

注意:起始基因序列 start 默认是有效的,但是它并不一定会出现在基因库中。

输入输出样例:

示例 1:
输入 :start = "AACCGGTT", end = "AACCGGTA", bank = ["AACCGGTA"]
输出:1

示例 2:
输入 :start = "AACCGGTT", end = "AAACGGTA", bank = ["AACCGGTA","AACCGCTA","AAACGGTA"]
输出:2

示例 3:
输入 :start = "AAAAACCC", end = "AACCCCCC", bank = ["AAAACCCC","AAACCCCC","AACCCCCC"]
输出:3

提示:

start.length== 8

end.length== 8

0 <= bank.length <= 10

bank[i].length == 8

start、end 和 bank[i] 仅由字符 ['A', 'C', 'G', 'T'] 组成

题解:

解题思路:

思路一(广度优先搜索(BFS)):

1、使用 广度优先搜索(BFS) 来解决这个问题的原因是,我们的目标是找到从 start 到 end 的 最短路径(即最少的基因变化次数)。

  • 这个问题本质上是一个最短路径问题。我们从起始基因序列 start 出发,目标是通过一系列的基因变化最终得到目标基因序列 end,每次变化只能修改一个字符,而且变化后的基因序列必须出现在基因库 bank 中。
  • 在图中,每个基因序列都可以看作是一个节点,两个基因序列之间的边表示它们只相差一个字符。

2、复杂度分析:

① 时间复杂度:O(N),需要遍历 N 个基因,每个基因产生最多 32 个变异,因此 BFS 的总遍历次数为 O(N * 32)。

② 空间复杂度:O(N),bank_set 的空间复杂度为 O(N),为了避免重复访问基因,代码维护了一个 unordered_set 为 O(N),队列的空间复杂度也是 O(N)。

代码实现

代码实现(思路一(广度优先搜索(BFS))):
cpp 复制代码
class Solution {
public:
    // 函数minMutation计算从startGene到endGene的最小变异步数
    int minMutation(string startGene, string endGene, vector<string>& bank) {
        
        // 将基因库bank转换为一个unordered_set方便快速查找
        unordered_set<string> bank_set(bank.begin(), bank.end());
        unordered_set<string> visited;

        // 如果起始基因和目标基因相同,直接返回0
        if(startGene == endGene) {
            return 0;
        }

        // 如果目标基因不在基因库中,返回-1
        if (!bank_set.count(endGene)) {
            return -1;
        }

        // 初始化队列用于广度优先搜索(BFS),开始时队列只包含起始基因
        queue<string> Q;
        Q.push(startGene);        
        visited.insert(startGene);  // 记录已访问的基因,防止重复访问
        int step = 1;  // 步数初始化为1(表示当前基因本身作为第一步)

        // BFS循环,直到队列为空
        while (!Q.empty()) {
            int size = Q.size();  // 当前队列的大小,即当前层的基因数
            for (int i = 0; i < size; i++) {  // 遍历当前层的所有基因
                string curStr = Q.front();
                Q.pop();
                
                // 对当前基因的每一位(8个位置)进行变异
                for (int j = 0; j < 8; j++) {
                    // 遍历所有可能的字符('A', 'C', 'G', 'T')
                    for (auto &c : "ACGT") {
                        // 只有在当前基因和待变异的字符不相同的时候才进行变异
                        if(curStr[j] != c) {
                            string next = curStr;  // 生成变异后的新基因
                            next[j] = c;  // 将第j位基因变为字符c
                            
                            // 如果新基因等于目标基因,返回当前的步数
                            if (next == endGene) {
                                return step;
                            }

                            // 如果新基因在基因库中并且未被访问过,则加入队列
                            if (!visited.count(next) && bank_set.count(next)) {
                                Q.push(next);
                                visited.insert(next);  // 标记新基因为已访问
                            }
                        }
                    }
                }
            }
            step++;  // 增加步数,表示当前层的所有基因都已处理
        }
        
        // 如果没有找到目标基因,返回-1
        return -1;
    }
};
以思路一为例进行调试
cpp 复制代码
#include<iostream>
#include<vector>
#include<unordered_set>
#include<queue>
using namespace std;

class Solution {
public:
    // 函数minMutation计算从startGene到endGene的最小变异步数
    int minMutation(string startGene, string endGene, vector<string>& bank) {
        
        // 将基因库bank转换为一个unordered_set方便快速查找
        unordered_set<string> bank_set(bank.begin(), bank.end());
        unordered_set<string> visited;

        // 如果起始基因和目标基因相同,直接返回0
        if(startGene == endGene) {
            return 0;
        }

        // 如果目标基因不在基因库中,返回-1
        if (!bank_set.count(endGene)) {
            return -1;
        }

        // 初始化队列用于广度优先搜索(BFS),开始时队列只包含起始基因
        queue<string> Q;
        Q.push(startGene);        
        visited.insert(startGene);  // 记录已访问的基因,防止重复访问
        int step = 1;  // 步数初始化为1(表示当前基因本身作为第一步)

        // BFS循环,直到队列为空
        while (!Q.empty()) {
            int size = Q.size();  // 当前队列的大小,即当前层的基因数
            for (int i = 0; i < size; i++) {  // 遍历当前层的所有基因
                string curStr = Q.front();
                Q.pop();
                
                // 对当前基因的每一位(8个位置)进行变异
                for (int j = 0; j < 8; j++) {
                    // 遍历所有可能的字符('A', 'C', 'G', 'T')
                    for (auto &c : "ACGT") {
                        // 只有在当前基因和待变异的字符不相同的时候才进行变异
                        if(curStr[j] != c) {
                            string next = curStr;  // 生成变异后的新基因
                            next[j] = c;  // 将第j位基因变为字符c
                            
                            // 如果新基因等于目标基因,返回当前的步数
                            if (next == endGene) {
                                return step;
                            }

                            // 如果新基因在基因库中并且未被访问过,则加入队列
                            if (!visited.count(next) && bank_set.count(next)) {
                                Q.push(next);
                                visited.insert(next);  // 标记新基因为已访问
                            }
                        }
                    }
                }
            }
            step++;  // 增加步数,表示当前层的所有基因都已处理
        }
        
        // 如果没有找到目标基因,返回-1
        return -1;
    }
};


int main(int argc, char const *argv[])
{
    string startGene="AAAAACCC";
    string endGene="AACCCCCC";
    vector<string> bank={"AAAACCCC","AAACCCCC","AACCCCCC"};
    Solution s;
    int step= s.minMutation(startGene,endGene,bank);
    cout<<"step="<<step<<endl;
    return 0;
}

LeetCode 面试经典 150_LeetCode 面试经典 150_图的广度优先搜索_最小基因变化(93_433)原题链接

欢迎大家和我沟通交流(✿◠‿◠)

相关推荐
Thera7776 小时前
状态机(State Machine)详解:原理、优缺点与 C++ 实战示例
开发语言·c++
linux开发之路6 小时前
C++高性能日志库开发实践
c++·c++项目·后端开发·c++新特性·c++校招
刻BITTER7 小时前
在TRAE 上安装PlatformIO
c++·单片机·嵌入式硬件·arduino
永远都不秃头的程序员(互关)7 小时前
C++动态数组实战:从手写到vector优化
c++·算法
水力魔方8 小时前
武理排水管网模拟分析系统应用专题5:模型克隆与并行计算
数据库·c++·算法·swmm
OliverH-yishuihan9 小时前
在win10上借助WSL用VS2019开发跨平台项目实例
linux·c++·windows
汉克老师9 小时前
GESP2025年12月认证C++二级真题与解析(编程题1 (环保能量球))
c++·gesp二级·gesp2级
郝学胜-神的一滴9 小时前
Linux进程与线程控制原语对比:双刃出鞘,各显锋芒
linux·服务器·开发语言·数据结构·c++·程序人生
青岛少儿编程-王老师10 小时前
CCF编程能力等级认证GESP—C++2级—20251227
java·开发语言·c++
javachen__10 小时前
341-十道经典程序设计题目
数据结构·c++·算法