最小基因变化
难度:
标签:
题目描述
基因序列可以表示为一条由 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']
组成
代码结果
运行时间: 21 ms, 内存: 16.2 MB
/*
思路:
1. 由于Java Stream不适合直接处理BFS搜索,我们主要使用Stream来初始化数据结构。
2. 使用Stream初始化基因库,并过滤出有效的序列。
*/
import java.util.*;
import java.util.stream.Collectors;
public class GeneMutationStream {
public int minMutation(String start, String end, String[] bank) {
Set<String> bankSet = Arrays.stream(bank).collect(Collectors.toSet());
if (!bankSet.contains(end)) return -1;
char[] genes = new char[]{'A', 'C', 'G', 'T'};
Queue<String> queue = new LinkedList<>();
queue.offer(start);
Set<String> visited = new HashSet<>();
visited.add(start);
int level = 0;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
String current = queue.poll();
if (current.equals(end)) return level;
char[] currentArray = current.toCharArray();
for (int j = 0; j < currentArray.length; j++) {
char old = currentArray[j];
for (char c : genes) {
currentArray[j] = c;
String next = new String(currentArray);
if (bankSet.contains(next) && !visited.contains(next)) {
visited.add(next);
queue.offer(next);
}
}
currentArray[j] = old;
}
}
level++;
}
return -1;
}
}
解释
方法:
这个题解采用深度优先搜索的方法来寻找从起始基因序列到目标基因序列的最短变化路径。首先判断目标基因序列是否在基因库中,如果不在则直接返回-1。然后构建一个邻接矩阵表示基因库中每两个基因序列之间是否只差一个字符。接着从起始基因序列开始进行深度优先搜索,每次变化一个字符,搜索所有可能的变化路径,同时记录变化的次数,找到最短的变化路径。
时间复杂度:
O(n^2)
空间复杂度:
O(n^2)
代码细节讲解
🦆
为什么在构建邻接矩阵时,只计算每两个基因序列之间相异字符的数量,并判断是否为1?这种方法是否能准确表示只有一步变化的基因序列?
▷🦆
在深度优先搜索中,为什么选择递归而不是迭代?这种选择对性能有何影响?
▷🦆
题解中设置了一个变量 `ans` 初始化为10,这个值的选择有什么特殊意义吗?是否存在更好的初始值设定方式?
▷🦆
如果 `startGene` 等于 `endGene`,算法的行为会如何变化?题解中是否考虑了这种特殊情况?
▷相关问题
单词接龙
字典 wordList
中从单词 beginWord
和 endWord
的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk
:
- 每一对相邻的单词只差一个字母。
- 对于
1 <= i <= k
时,每个si
都在wordList
中。注意,beginWord
不需要在wordList
中。 sk == endWord
给你两个单词 beginWord
和 endWord
和一个字典 wordList
,返回 从 beginWord
到 endWord
的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0
。
示例 1:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] 输出:5 解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
示例 2:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"] 输出:0 解释:endWord "cog" 不在字典中,所以无法进行转换。
提示:
1 <= beginWord.length <= 10
endWord.length == beginWord.length
1 <= wordList.length <= 5000
wordList[i].length == beginWord.length
beginWord
、endWord
和wordList[i]
由小写英文字母组成beginWord != endWord
wordList
中的所有字符串 互不相同