原題地址
https://leetcode.com/problems/repeated-dna-sequences/
題目描述
All DNA is composed of a series of nucleotides abbreviated as A, C, G, and T, for example: “ACGAATTCCG”. When studying DNA, it is sometimes useful to identify repeated sequences within the DNA.
所有的DNA都是由簡寫爲A``C``G``T
的核苷酸構成的,例如ACGAATTCCG
。在研究DNA時,辨別DNA中重複的序列在有些時候是很有用的。
Write a function to find all the 10-letter-long sequences (substrings) that occur more than once in a DNA molecule.
編寫一個程序來找到一個DNA分子中出現次數超過一次的長度爲10的子序列(子串)。
For example,
例如,
Given s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
,
給出 s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
,
Return:
返回:
["AAAAACCCCC", "CCCCCAAAAA"].
Tags Hash Table
Bit Manipulation
解題思路
嗯,這個題,如果時間上沒有要求的話,用find和substr搞定是沒問題的,然而在測試數據較大的時候這種方法必然會超時。
這個題的標籤是Hash Table和Bit Manipulation,參考https://leetcode.com/discuss/24478/i-did-it-in-10-lines-of-c中的方案,現做如下整理。
測試數據中只會出現4種字符’A’,’C’,’G’,’T’,其ASC值分別爲65,67,71,84,對應的二進制位如下所示,這裏僅列取32位情況下的低8位,前24位全部爲0。
A --- 65 --- 0100 0001
C --- 67 --- 0100 0011
G --- 71 --- 0100 0111
T --- 84 --- 0101 0100
我們可以發現,僅通過低3位就能把4種字符區分開來,題目又要求了是長度爲10的子串
(10-letter-long
),試想爲什麼不是11或者更大呢,因爲10個字符每個用3位表示一個字符的話剛好是30位,而一個int是32位,剛好能放下,如果是11+就放不下了。因此,我們可以把一個長度爲10的字符串映射成一個int數,用其低30位表示這個串,作爲這個串的key,然後把key存在Hash Table中,當key重複出現時就代表子串重複出現了。
詳細代碼請見下文。
除了上述方法外,受其啓發,我們其實只用2個二進制位就可以唯一區分4中字符,比如A-00,C-01,G-10,T-11
,這樣,我們只用int的低20位就可以表示一個字符串的key,這種方法甚至可以最多用於處理長度爲16的子串。比上面一種方法略顯複雜的是,需要手動將ACGT映射成2位二進制數,方法也是多種多樣的。比如:
方法一
/** @return 字符到2位二進制數的映射 */
int function(char c) {
int ret = 0;
switch (c) {
case 'A': break;
case 'G': ret = 1; break;
case 'C': ret = 2; break;
case 'T': ret = 3; break;
}
return ret;
}
方法二
/* 數組映射,調用時使用nums[c - 65]即可得到字符c的映射碼 */
int nums[20];
nums[0] = 0;
nums[2] = 1;
nums[6] = 2;
nums[19] = 3;
除此外,在討論貼中還看到了另外一種很巧妙的映射方法:
方法三
/**
* (s[i] - 64) % 5完成映射
* A : ('A' - 64) % 5 = 1 (mod 5) = 1 = 01
* B : ('C' - 64) % 5 = 3 (mod 5) = 3 = 11
* C : ('G' - 64) % 5 = 7 (mod 5) = 2 = 10
* D : ('T' - 64) % 5 = 20 (mod 5) = 0 = 00
*/
key = key << 2 & 0xfffff | (s[i] - 64) % 5;
代碼
代碼一
首先是用ACGT的ASC碼的後3位來映射的解法。在代碼編寫中,對於最開始的9個字符是爲了構造第一個key而做功,一般說來從第10個字符開始纔開始判定key的重複出現情況。然而由於AGCT的映射碼均爲三位且沒有任何一個碼是000,因此在前面9個字符也是可以與其他字符一致處理的,不會出現誤判。一致處理的代碼大致如下:
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
vector<string> strs;
unordered_map<int, int> map;
int key = 0;
for (int i = 0, end = s.size(); i < end; ++i) {
key = ((key << 3) | (s[i] & 0x7)) & 0x3fffffff;
if (map[key]++ == 1)
strs.push_back(s.substr(i - 9, 10));
}
return strs;
}
};
不一致處理的代碼大概如下,先處理前9個字符,然後處理後面的:
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
vector<string> strs;
if (s.size() <= 10) return strs;
unordered_map<int, int> map;
int key = 0, i = 0;
for (; i < 9; ++i)
key = ((key << 3) | (s[i] & 0x7)) & 0x3fffffff;
for (int end = s.size(); i < end; ++i) {
key = ((key << 3) | (s[i] & 0x7)) & 0x3fffffff;
if (map[key]++ == 1)
strs.push_back(s.substr(i - 9, 10));
}
return strs;
}
};
完整代碼https://github.com/Orange1991/leetcode/blob/master/187/cpp/main.cpp
很難說到底哪種代碼更好。如果從代碼精簡的方面來看,前面一個代碼數量更少;但是前面一種代碼對於前9個字符也會把key放入map中且會判定是否key重複出現,這個操作是多餘的,相比於後面的解法來說就多了一些無用的操作,在實際的運行中也證實了這一點,前一種時間爲100ms,而後一種爲96ms。另外,對於前面一種方案,是否可以將前9個字符與其他字符一致處理而不致於誤判,還要看AGCT映射碼中是否有全0的碼,如果有則不能一致處理,就拿接下來馬上要說的解法二來說,我們把A映射爲00,假如我們的串是AA(你沒看錯,就是隻有2位的串),那對於第一個字符,key計算後得到結果爲0(你沒看錯,就是0),我們把0放入map並且其value爲0,然後,在第二個字符,key計算後仍然爲0,這時候根據規則,以第二個A爲結尾的10個字符要放入結果集中,這時候程序一定會崩潰的(s.substr(-8, 10))。因此,我們還是直接用後面一種解法就好,代碼不過多了兩行,而且不會出現誤判,哪怕映射碼中有全0碼也沒問題。
代碼二 自定義映射碼
在前面解題思路中已經說的比較詳細了,因此不再贅述,僅列出代碼
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
vector<string> strs;
if (s.size() <= 10) return strs;
unordered_map<int, int> map;
int key = 0, i = 0;
/* 映射邏輯: A-01, C-11, G-10, T-00 */
for (; i < 9; ++i)
key = ((key << 2) | ((s[i] - 64) % 5)) & 0xfffff;
for (int end = s.size(); i < end; ++i) {
key = ((key << 2) | ((s[i] - 64) % 5)) & 0xfffff;
if (map[key]++ == 1)
strs.push_back(s.substr(i - 9, 10));
}
return strs;
}
};
完整代碼https://github.com/Orange1991/leetcode/blob/master/187/cpp/solution2.cpp
參考
https://leetcode.com/discuss/24478/i-did-it-in-10-lines-of-c
http://blog.csdn.net/ljiabin/article/details/44488619
2015/8/5