题目:433.最小基因变化
基因序列可以表示为一条由 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)来解决。可以将每个基因序列看作图的一个节点,如果两个基因序列之间只相差一个字符,它们之间可以直接连接。问题就转化为从 start
序列到 end
序列的最短路径问题。
- 广度优先搜索(BFS):使用
BFS
找最短路径是一个合适的方法,因为BFS
的性质决定了当我们第一次到达终点时,路径就是最短的。使用队列存储BFS
的状态,每个状态由当前基因序列和变化次数组成。使用集合来存储基因库,以便于快速查找某个基因序列是否在基因库中。 - 邻接关系:两个基因序列之间如果仅有一个字符不同,就可以认为它们是相邻的。我们可以在
BFS
搜索过程中,每次只改变一个字符,并且检查新生成的序列是否在基因库中。 - 剪枝:每访问一个基因序列,我们要将它标记为已访问,以避免重复访问。
- 返回结果:如果
BFS
搜索到end
序列,返回当前的变化次数;如果搜索完成仍未找到,返回-1
。
- 时间复杂度:O(4kn),其中 k 是基因库中基因序列的个数,n 是基因序列的长度(通常为 8)
- 空间复杂度:O(k),其中 k 是基因库中基因序列的个数
代码
public int minMutation(String start, String end, String[] bank) {
// 将基因库存入哈希集合,便于快速查找
Set<String> bankSet = new HashSet<>(Arrays.asList(bank));
// 如果终点基因不在基因库中,无法完成变化
if (bank.length == 0 || !bankSet.contains(end)) {
return -1;
}
int result = 0;
// 初始化 BFS 队列,存放基因序列和对应的变化次数
Queue<String> queue = new LinkedList<>();
queue.offer(start);
// 用于标记已访问的基因序列
Set<String> visited = new HashSet<>();
visited.add(start);
// 基因的四个可能字符
char[] genes = new char[]{'A', 'C', 'G', 'T'};
// 开始 BFS
while (!queue.isEmpty()) {
int size = queue.size();
// 遍历当前层次的基因序列
for (int i = 0; i < size; i++) {
String originString = queue.poll();
// 如果当前基因序列是目标序列,返回变化次数
if (originString.equals(end)) {
return result;
}
// 对当前序列的每一个字符进行变换
char[] currentArray = originString.toCharArray();
for (int j = 0; j < originString.length(); j++) {
char c = currentArray[j];
// 尝试将当前字符替换为基因字符集合中的字符
for (char gene : genes) {
// 不替换相同的字符
if (gene == c) {
continue;
}
// 替换字符
currentArray[j] = gene;
String newString = new String(currentArray);
// 如果新的基因在基因库中,且没有访问过,加入队列
if (!visited.contains(newString) && bankSet.contains(newString)) {
queue.offer(newString);
visited.add(newString);
}
}
// 恢复原始字符
currentArray[j] = c;
}
}
// 走完当前层,变化次数加1
result++;
}
// 无法从 start 变到 end
return -1;
}