[LeetCode] Minimum Genetic Mutation 最小基因變化


 

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:

  1. Starting point is assumed to be valid, so it might not be included in the bank.
  2. If multiple mutations are needed, all mutations during in the sequence must be valid.
  3. 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;
    }
};

 

類似題目:

Word Ladder

Word Ladder II

Permutations

 

參考資料:

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

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM