最小基因变化
1.题目内容
基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 'A'、'C'、'G' 和 'T' 之一。
假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。
- 例如,
"AACCGGTT" --> "AACCGGTA" 就是一次基因变化。
另有一个基因库 bank 记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。(变化后的基因必须位于基因库 bank 中)
给你两个基因序列 start 和 end ,以及一个基因库 bank ,请你找出并返回能够使 start 变化为 end 所需的最少变化次数。如果无法完成此基因变化,返回 -1 。
注意:起始基因序列 start 默认是有效的,但是它并不一定会出现在基因库中。
示例 1:
1 2
| 输入:start = "AACCGGTT", end = "AACCGGTA", bank = ["AACCGGTA"] 输出:1
|
示例 2:
1 2
| 输入:start = "AACCGGTT", end = "AAACGGTA", bank = ["AACCGGTA","AACCGCTA","AAACGGTA"] 输出:2
|
示例 3:
1 2
| 输入: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'] 组成
2.解法
广度优先搜索
思路及算法
经过分析可知,题目要求将一个基因序列 A 变化至另一个基因序列 B,需要满足以下条件:
- 序列 A 与 序列 B 之间只有一个字符不同;
- 变化字符只能从 ‘A’, ‘C’, ‘G’, ‘T’ 中进行选择;
- 变换后的序列 B 一定要在字符串数组 bank 中。
根据以上变换规则,我们可以进行尝试所有合法的基因变化,并找到最小的变换次数即可。步骤如下:
如果 start 与 end 相等,此时直接返回 0;如果最终的基因序列不在 bank 中,则此时按照题意要求,无法生成,直接返回 −1;
首先我们将可能变换的基因 s 从队列中取出,按照上述的变换规则,尝试所有可能的变化后的基因,比如一个 AACCGGTA,我们依次尝试改变基因 s 的一个字符,并尝试所有可能的基因变化序列 s0,s1,s2,⋯ ,si,⋯ ,s23,变化一次最多可能会生成 3×8=24 种不同的基因序列。
我们需要检测当前生成的基因序列的合法性 si,首先利用哈希表检测 si 是否在数组 bank 中,如果是则认为该基因合法,否则该变化非法直接丢弃;其次我们还需要用哈希表记录已经遍历过的基因序列,如果该基因序列已经遍历过,则此时直接跳过;如果合法且未遍历过的基因序列,则我们将其加入到队列中。
如果当前变换后的基因序列与 end 相等,则此时我们直接返回最小的变化次数即可;如果队列中所有的元素都已经遍历完成还无法变成 end,则此时无法实现目标变化,返回 −1。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| //C++ class Solution { public: int minMutation(string start, string end, vector<string>& bank) { unordered_set<string> cnt; unordered_set<string> visited; char keys[4] = {'A', 'C', 'G', 'T'}; for (auto & w : bank) { cnt.emplace(w); } if (start == end) { return 0; } if (!cnt.count(end)) { return -1; } queue<string> qu; qu.emplace(start); visited.emplace(start); int step = 1; while (!qu.empty()) { int sz = qu.size(); for (int i = 0; i < sz; i++) { string curr = qu.front(); qu.pop(); for (int j = 0; j < 8; j++) { for (int k = 0; k < 4; k++) { if (keys[k] != curr[j]) { string next = curr; next[j] = keys[k]; if (!visited.count(next) && cnt.count(next)) { if (next == end) { return step; } qu.emplace(next); visited.emplace(next); } } } } } step++; } return -1; } };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| //Java class Solution { public int minMutation(String start, String end, String[] bank) { Set<String> cnt = new HashSet<String>(); Set<String> visited = new HashSet<String>(); char[] keys = {'A', 'C', 'G', 'T'}; for (String w : bank) { cnt.add(w); } if (start.equals(end)) { return 0; } if (!cnt.contains(end)) { return -1; } Queue<String> queue = new ArrayDeque<String>(); queue.offer(start); visited.add(start); int step = 1; while (!queue.isEmpty()) { int sz = queue.size(); for (int i = 0; i < sz; i++) { String curr = queue.poll(); for (int j = 0; j < 8; j++) { for (int k = 0; k < 4; k++) { if (keys[k] != curr.charAt(j)) { StringBuffer sb = new StringBuffer(curr); sb.setCharAt(j, keys[k]); String next = sb.toString(); if (!visited.contains(next) && cnt.contains(next)) { if (next.equals(end)) { return step; } queue.offer(next); visited.add(next); } } } } } step++; } return -1; } }
|
复杂度分析
时间复杂度:O(C×n×m),其中 n 为基因序列的长度,m 为数组 bank 的长度。对于队列中的每个合法的基因序列每次都需要计算 C×n 种变化,在这里 C=4;队列中最多有 m 个元素,因此时间复杂度为 O(C×n×m)。
空间复杂度:O(n×m),其中 n 为基因序列的长度,m 为数组 bank 的长度。合法性的哈希表中一共存有 m 个元素,队列中最多有 m 个元素,每个元素的空间为 O(n);队列中最多有 m 个元素,每个元素的空间为 O(n),因此空间复杂度为 O(n×m)。