一、题目描述
基因序列可以表示为一条由
8
个字符组成的字符串,其中每个字符都是'A'
、'C'
、'G'
和'T'
之一。
假设我们需要调查从基因序列start
变为end
所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。
● 例如,"AACCGGTT" --> "AACCGGTA"
就是一次基因变化。
另有一个基因库bank
记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。
给你两个基因序列start
和end
,以及一个基因库bank
,请你找出并返回能够使start
变化为end
所需的最少变化次数。如果无法完成此基因变化,返回-1
。
注意:起始基因序列start
默认是有效的,但是它并不一定会出现在基因库中。
示例:
输入:start = "AACCGGTT"
,end = "AAACGGTA"
,
bank = ["AACCGGTA","AACCGCTA","AAACGGTA"]
输出:2
二、分析与思路
今天的每日一题是广度优先搜索的知识,但是对这些知识点并不熟悉,只是知道要用到 队列 的知识,因此又是抄官方题解、理解官方题解的一天;
变化规则:给定一个字符串 start
,每次只能改变一个字符得到一个新的字符串 next
;这个最字符串只有在字符串库 bank
里面才算一个合法的字符串;并且每个变化字符只能 A’, ‘C’, ‘G’, ‘T’
中进行选择;这里的字符串也就是题目中提到的基因序列。
根据以上变化规则,我们可以进行尝试所有合法的基因变化,并找到最小的变化次数;
(1)如果 start
与 end
相等,此时直接返回 0
;如果最终的基因序列不在 bank
中,则此时按照题意要求,无法生成,直接返回 -1
;
(2)首先我们将可能变换的基因 s
从队列中取出,按照上述的变换规则,尝试所有可能的变化后的基因,比如一个
AACCGGTA
\texttt{AACCGGTA}
AACCGGTA,我们依次尝试改变基因 s
的一个字符,并尝试所有可能的基因变化序列
s
0
,
s
1
,
s
2
,
⋯
,
s
i
,
⋯
,
s
23
s_0, s_1, s_2, \cdots, s_i, \cdots, s_{23}
s0,s1,s2,⋯,si,⋯,s23,变化一次最多可能会生成
3
×
8
=
24
3 \times8=24
3×8=24 种不同的基因序列;
(3)我们需要检测当前生成的基因序列的合法性
s
i
s_i
si,首先利用哈希表检测
s
i
s_i
si 是否在数组
bank
\textit{bank}
bank 中,如果是则认为该基因合法,否则把非法直接丢弃;其次我们还需要用哈希表记录已经遍历过的基因序列,如果该基因序列已经遍历过,则此时直接跳过;如果是合法且未遍历过的基因序列,那么我们将其加入到队列中;
(4)如果当前变换后的基因序列与
end
\textit{end}
end 相等,此时我们直接返回最小的变化次数即可;如果队列中所有的元素都已经遍历完成还无法变成
end
\textit{end}
end ,此时无法实现目标变化,返回
−
1
-1
−1。
三、代码实现
class Solution {
public:
int minMutation(string start, string end, vector<string>& bank) {
unordered_set<string> cnt, visited;
char keys[4] = {'A', 'T', 'C', 'G'};
for(auto &str : bank){
cnt.insert(str);
}
// 去除一些特殊情况
if(start == end){ // (1)
return 0;
}
if(cnt.count(end) == 0){
return -1;
}
queue<string> q;
q.push(start);
visited.insert(start);
int step = 1;
while(!q.empty()){
int sz = q.size();
int i, j, k;
for(i = 0; i < sz; ++i){ // (2)
string now = q.front();
q.pop();
for(j = 0; j < 8; ++j){
for(k = 0; k < 4; ++k){
if(keys[k] != now[j]){
string next = now;
next[j] = keys[k];
if(!visited.count(next) && cnt.count(next)){ // (3)
if(next == end){ // (4)
return step;
}
q.push(next);
visited.insert(next);
}
}
}
}
}
++step;
}
return -1;
}
};
四、总结
(1)广度优先搜索,一般用来求解 树的层序遍历问题、最短路问题、连通性问题、拓扑排序,这也是接下来的学习重点。后序继续更新;
(2)
set
\texttt{set}
set 的插入操作是 emplace()
或者 insert()
。注意对于
set
\texttt{set}
set 中的 count()
命令的使用。