一、题目
基因序列可以表示为一条由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']
组成
二、代码
【1】广度优先搜索: 经过分析可知,题目要求将一个基因序列A
变化至另一个基因序列B
,需要满足以下条件:
1、序列A
与 序列B
之间只有一个字符不同;
2、变化字符只能从A
, C
, G
, T
中进行选择;
3、变换后的序列B
一定要在字符串数组bank
中。
根据以上变换规则,我们可以进行尝试所有合法的基因变化,并找到最小的变换次数即可。步骤如下:
1、如果start
与end
相等,此时直接返回0
;如果最终的基因序列不在bank
中,则此时按照题意要求,无法生成,直接返回−1
;
2、首先我们将可能变换的基因 sss 从队列中取出,按照上述的变换规则,尝试所有可能的变化后的基因,比如一个AACCGGTA
,我们依次尝试改变基因s
的一个字符,并尝试所有可能的基因变化序列s0
,s1
,s2
,⋯
,si
,⋯
,s23
,变化一次最多可能会生成3×8=24
种不同的基因序列。
3、我们需要检测当前生成的基因序列的合法性si
,首先利用哈希表检测si
是否在数组bank
中,如果是则认为该基因合法,否则改变化非法直接丢弃;其次我们还需要用哈希表记录已经遍历过的基因序列,如果该基因序列已经遍历过,则此时直接跳过;如果合法且未遍历过的基因序列,则我们将其加入到队列中。
4、如果当前变换后的基因序列与end
相等,则此时我们直接返回最小的变化次数即可;如果队列中所有的元素都已经遍历完成还无法变成end
,则此时无法实现目标变化,返回−1
。
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)
。
【2】预处理优化: 经过分析可知,题目要求将一个基因序列A
变化至另一个基因序列B
,需要满足一下条件:
1、A
与 序列B
之间只有一个字符不同;
2、变化字符只能从A
, C
, G
, T
中进行选择;
3、变换后的序列B
一定要在字符串数组bank
中。
已知方法一中广度优先搜索方法,我们可以对bank
进行预处理,只在合法的基因变化进行搜索即可。由于题目中给定的bank
基因库的长度较小,因此可以直接在对bank
进行预处理,找到基因库中的每个基因的合法变换,而不需要像方法一中每次都需要去计算基因的变化序列,我们将每个基因的合法变化关系存储在邻接表adj
中,每次基因变化搜索只在adj
中进行即可。
class Solution {public int minMutation(String start, String end, String[] bank) {int m = start.length();int n = bank.length;List<Integer>[] adj = new List[n];for (int i = 0; i < n; i++) {adj[i] = new ArrayList<Integer>();}int endIndex = -1;for (int i = 0; i < n; i++) {if (end.equals(bank[i])) {endIndex = i;}for (int j = i + 1; j < n; j++) {int mutations = 0;for (int k = 0; k < m; k++) {if (bank[i].charAt(k) != bank[j].charAt(k)) {mutations++;}if (mutations > 1) {break;}}if (mutations == 1) {adj[i].add(j);adj[j].add(i);}}}if (endIndex == -1) {return -1;}Queue<Integer> queue = new ArrayDeque<Integer>();boolean[] visited = new boolean[n];int step = 1;for (int i = 0; i < n; i++) {int mutations = 0;for (int k = 0; k < m; k++) {if (start.charAt(k) != bank[i].charAt(k)) {mutations++;}if (mutations > 1) {break;}}if (mutations == 1) {queue.offer(i);visited[i] = true;}} while (!queue.isEmpty()) {int sz = queue.size();for (int i = 0; i < sz; i++) {int curr = queue.poll();if (curr == endIndex) {return step;}for (int next : adj[curr]) {if (visited[next]) {continue;}visited[next] = true;queue.offer(next);}}step++;}return -1;}
}
时间复杂度: O(m×n2)
,其中m
为基因序列的长度,n
为数组bank
的长度。计算合法的基因变化adj
需要的时间为O(m×n2)
,广度优先搜索时,队列中最多有n
个元素,需要的时间为O(n)
,因此时间复杂度为O(m×n2)
。
空间复杂度: O(n2)
,其中n
为数组bank
的长度。计算合法的基因变化adj
需要的空间为O(n^2)
,队列中最多有n
个元素,因此空间复杂度为O(n^2)
。