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)原题链接

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

相关推荐
kingmax542120082 小时前
2025年下半年12月6号-高中数学教师资格证面试试讲真题与考情分析【全网最全】
数学建模·面试·教资
steins_甲乙3 小时前
C++并发编程
开发语言·c++
CoderYanger3 小时前
动态规划算法-两个数组的dp(含字符串数组):42.不相交的线
java·算法·leetcode·动态规划·1024程序员节
常先森3 小时前
RAG 表格解析最佳实践:标题识别、表头推断与语义重建全指南
面试·架构·agent
while(1){yan}3 小时前
多线程CAS八股文
java·开发语言·面试
南莺莺4 小时前
二叉排序树的创建和基本操作---C++实现
数据结构·c++·算法··二叉排序树
仰泳的熊猫4 小时前
1061 Dating
数据结构·c++·算法·pat考试
Fcy6484 小时前
二叉搜索树(C++实现)
开发语言·数据结构·c++·二叉搜索树