A gene string can be represented by an 8-character long string, with choices from "A"
, "C"
, "G"
, "T"
.
Suppose we need to investigate about a mutation (mutation from "start" to "end"), where ONE mutation is defined as ONE single character changed in the gene string.
For example, "AACCGGTT"
-> "AACCGGTA"
is 1 mutation.
Also, there is a given gene "bank", which records all the valid gene mutations. A gene must be in the bank to make it a valid gene string.
Now, given 3 things - start, end, bank, your task is to determine what is the minimum number of mutations needed to mutate from "start" to "end". If there is no such a mutation, return -1.
Note:
- Starting point is assumed to be valid, so it might not be included in the bank.
- If multiple mutations are needed, all mutations during in the sequence must be valid.
- You may assume start and end string is not the same.
Example 1:
start: "AACCGGTT" end: "AACCGGTA" bank: ["AACCGGTA"] return: 1
Example 2:
start: "AACCGGTT" end: "AAACGGTA" bank: ["AACCGGTA", "AACCGCTA", "AAACGGTA"] return: 2
Example 3:
start: "AAAAACCC" end: "AACCCCCC" bank: ["AAAACCCC", "AAACCCCC", "AACCCCCC"] return: 3
這道題跟之前的 Word Ladder 完全是一道題啊,換個故事就直接來啊,越來不走心了啊。不過博主做的時候並沒有想起來是之前一樣的題,而是先按照腦海里第一個浮現出的思路做的,發現也通過OJ了。博主使用的一種BFS的搜索,先建立bank數組的距離場,這里距離就是兩個字符串之間不同字符的個數。然后以start字符串為起點,向周圍距離為1的點擴散,采用BFS搜索,每擴散一層,level自加1,當擴散到end字符串時,返回當前level即可。注意我們要把start字符串也加入bank中,而且此時我們也知道start的坐標位置,bank的最后一個位置,然后在建立距離場的時候,調用一個count子函數,用來統計輸入的兩個字符串之間不同字符的個數,注意dist[i][j]和dist[j][i]是相同,所以我們只用算一次就行了。然后我們進行BFS搜索,用一個visited集合來保存遍歷過的字符串,注意檢測距離的時候,dist[i][j]和dist[j][i]只要有一個是1,就可以了,參見代碼如下:
解法一:
class Solution { public: int minMutation(string start, string end, vector<string>& bank) { if (bank.empty()) return -1; bank.push_back(start); int res = 0, n = bank.size(); queue<int> q{{n - 1}}; unordered_set<int> visited; vector<vector<int>> dist(n, vector<int>(n, 0)); for (int i = 0; i < n; ++i) { for (int j = i + 1; j < n; ++j) { dist[i][j] = count(bank[i], bank[j]); } } while (!q.empty()) { ++res; for (int i = q.size(); i > 0; --i) { int t = q.front(); q.pop(); visited.insert(t); for (int j = 0; j < n; ++j) { if ((dist[t][j] != 1 && dist[j][t] != 1) || visited.count(j)) continue; if (bank[j] == end) return res; q.push(j); } } } return -1; } int count(string word1, string word2) { int cnt = 0, n = word1.size(); for (int i = 0; i < n; ++i) { if (word1[i] != word2[i]) ++cnt; } return cnt; } };
下面這種解法跟之前的那道 Word Ladder 是一樣的,也是用的BFS搜索。跟上面的解法不同之處在於,對於遍歷到的字符串,我們不再有距離場,而是對於每個字符,我們都嘗試將其換為一個新的字符,每次只換一個,這樣會得到一個新的字符串,如果這個字符串在bank中存在,說明這樣變換是合法的,加入visited集合和queue中等待下一次遍歷,記得在下次置換字符的時候要將之前的還原。我們在queue中取字符串出來遍歷的時候,先檢測其是否和end相等,相等的話返回level,參見代碼如下:
解法二:
class Solution { public: int minMutation(string start, string end, vector<string>& bank) { if (bank.empty()) return -1; vector<char> gens{'A','C','G','T'}; unordered_set<string> s{bank.begin(), bank.end()}; unordered_set<string> visited; queue<string> q{{start}}; int level = 0; while (!q.empty()) { for (int i = q.size(); i > 0; --i) { string t = q.front(); q.pop(); if (t == end) return level; for (int j = 0; j < t.size(); ++j) { char old = t[j]; for (char c : gens) { t[j] = c; if (s.count(t) && !visited.count(t)) { visited.insert(t); q.push(t); } } t[j] = old; } } ++level; } return -1; } };
再來看一種遞歸的解法,跟 Permutations 中的解法一有些類似,是遍歷bank中的字符串,跟當前的字符串cur相比較,調用isDiffOne()函數判斷,若正好跟cur相差一個字符,並且之前沒有訪問過,那么先在visited數組中標記為true,然后調用遞歸函數,若返回的不為-1,則用其更新結果res,因為-1代表無法變換成cur。調用完遞歸后恢復狀態,在visited數組中標記為false。循環結束后,看res的值,若還是n+1,表示無法更新,返回-1,否則返回res+1,因為這里的res是變換了一次后到達目標字符串的最小變化次數,所以要加上當前的這次變換。至於isDiffOne()函數就沒啥難度了,就是一個一個的比較,不同就累加計數器cnt,參見代碼如下:
解法三:
class Solution { public: int minMutation(string start, string end, vector<string>& bank) { if (bank.empty()) return -1; vector<bool> visited(bank.size(), false); return helper(start, end, bank, visited); } int helper(string cur, string end, vector<string>& bank, vector<bool>& visited) { if (cur == end) return 0; int n = bank.size(), res = n + 1; for (int i = 0; i < n; ++i) { if (visited[i] || !isDiffOne(bank[i], cur)) continue; visited[i] = true; int t = helper(bank[i], end, bank, visited); if (t != -1) res = min(res, t); visited[i] = false; } return res == n + 1 ? -1 : res + 1; } bool isDiffOne(string& s1, string& s2) { int cnt = 0, n = s1.size(); for (int i = 0; i < n; ++i) { if (s1[i] != s2[i]) ++cnt; if (cnt > 1) break; } return cnt == 1; } };
類似題目:
參考資料:
https://leetcode.com/problems/minimum-genetic-mutation/
https://leetcode.com/problems/minimum-genetic-mutation/discuss/91491/dfs-java
https://leetcode.com/problems/minimum-genetic-mutation/discuss/91484/java-solution-using-bfs