1. タイトル
遺伝子配列は、8
文字で構成される文字列として表すことができます。各文字はA
、C
、G
およびのいずれかになりますT
。start
遺伝子配列が変化しend
たときに生じる遺伝的変化を調査する必要があるとします。遺伝的変化とは、遺伝子配列内の 1 つの文字が変化することを意味します。
たとえば、
AACCGGTT
--> はAACCGGTA
遺伝的変化です。
すべての有効な遺伝子変化を記録するジーンバンクもあり、ジーンバンク内の遺伝子のみが有効な遺伝子配列です。(変更された遺伝子は遺伝子プールに存在する必要がありますbank
)。2 つの遺伝子配列start
とend
1 つの遺伝子ライブラリが与えられた場合、必要な変更を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']
2. コード
[1] 幅優先検索:分析後、質問では、ある遺伝子配列をA
別の遺伝子配列に変更する必要がありB
、次の条件を満たす必要があることがわかります:
1.A
配列B
間に文字の違いが 1 つだけある;
2 . 変更された文字は、 from A
、C
、G
、T
select fromのみです。
3. 変換されたシーケンスは、B
string 配列内に存在する必要がありますbank
。
上記の変換ルールに従って、すべての合法的な遺伝的変更を試して、最小数の変換を見つけることができます。手順は次のとおりです:
1.start
と がend
等しい場合は、この時点で直接戻ります0
; 最終的な遺伝子配列がbank
含まれていない場合は、質問の要件に従って、この時点では生成できず、直接戻ります−1
;
2.まず、上記の変換ルールに従って、変換された可能性のある遺伝子 sss をキューから削除し、すべての可能な変更された遺伝子 (1 つなど) を試し、遺伝子の 1 つの文字を順番にAACCGGTA
変更しs
、すべての可能な遺伝子変更シーケンスを試しますs0
。s1
、s2
、⋯
、si
、⋯
、 、s23
1 つの変更により、多くても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
最大でも 1 つの要素があるため、時間計算量は です。空間複雑度:、ここで、は遺伝子配列の長さ、は配列の長さです。正当なハッシュ テーブルには合計の要素があり、キューには最大 1 つの要素があり、各要素のスペースは です。キューには最大1 つの要素があり、各要素のスペースは です。空間の複雑さは です。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
と異なる文字が 1 つだけある;
2.変更された文字はA
、C
、 、G
、からのみ選択できますT
。
3. 変換されたシーケンスは、B
文字列配列内に存在する必要がありますbank
。
方法 1 の幅優先検索方法として知られるこの方法では、bank
合法的な遺伝的変化のみを前処理して検索できます。質問で指定された遺伝子ライブラリの長さはbank
短いため、bank
方法 1 のように毎回遺伝子の変更を計算する必要がなく、直接前処理して遺伝子ライブラリ内の各遺伝子の正当な変換を見つけることができます。各遺伝子の法的変化関係を隣接リストに保存し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
キューには最大でも 1 つの要素があり、必要な時間は であるO(n)
ため、時間計算量は ですO(m×n2)
。
空間計算量: O(n2)
、ここで、n
は配列の長さですbank
。合法的な遺伝的変化を計算するadj
ために必要な空間は でありO(n^2)
、キューには最大でも 1 つの要素があるためn
、空間複雑度は ですO(n^2)
。