描述 Description
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.
Write a function to find all the 10-letter-long sequences (substrings) that occur more than once in a DNA molecule.
For example,
Given s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT",
Return:
["AAAAACCCCC", "CCCCCAAAAA"].
分析 Analysis
方法1
窗口法
首先能想到一个简单直接的方法,用一个长度为10的窗口,从左到右扫描,放入 HashMap,并把计数器增一。最后,把 HashMap 中所有计数器大于1的字符串输出来。
时间复杂度 O(n), 由于HashMap中存储了所有长度为10的子串,所以空间复杂度O(10n)。
方法2
完美哈希
由于字符串中只存在 A, C, G, T 四种字符,我们可以把每个字符映射为2个bit:
A -> 00
C -> 01
G -> 10
T -> 11
每个长度为10的字符串,可以映射为 20 bits, 小于32位,因此可以把这个字符串映射到一个整数。这个方法时间复杂度依旧是O(n),但空间复杂度下降到了O(n)(n代表字符占用内存的话,这里应该是O(4n))。
方法3(推荐)
不是哈希,但是和哈希的原理是一样的,也是将10位字符串映射到int整数中保存,不过需要一些小技巧。
总共有4种可能的字符A, C, G 和 T,但是使用3个bits位来存储每个字符,这样10个字符的字符串需要30个bits(2个bits不用),同样也只需要1个int整数就可以保存10个字符串的映射(方法2是20个bits,有12个bits没用)。这样做的原因是可以更好地写出code,同时不需要哈希操作。
A, C, G, T的八进制表示 A is 0101, C is 0103, G is 0107, T is 0124. 4种字符八进制最后的一个数字(即二进制最后3位)都不同!
Note: 16进制分别对应为A is 0x41, C is 0x43, G is 0x47, T is 0x54。
我们可以使用s[i] & 7 得到最后的这个数字,只需要3bits存储。这样读入10个字符可以使用30个bits存储在一个int整数中,相当于对10长度字符串进行了一次hash。只要检测到此整数对应的map值已经是1了,就添加到结果中。还是不懂的话结合下面代码看吧。
时间复杂度O(n),空间复杂度O(4n)。
方法3变种
Note: 小编发现即使使用2bits,也是可以不使用hash字符串 。看A, C, G, T二进制的后3位,A 001, C 011, G 111, T 100,通过s[i] >> 1 & 3 (或者(s[i] - 'A' + 1) % 5)就可以得到倒数第2-3位的两个bits,A 00, C 01, G 11, T 10,居然都是不同的!所以同样不使用直接hash还是可以只使用20个bits存储的。直接这样其实不如不使用这种变种。
时间复杂度O(n),空间复杂度O(4n)。
但是变种方法如果使用bitmap方法,用字符bitmap就可以只使用24个bits(即3个char)存储,这样空间只需要O(3n)。
代码 Code
注意字符串<11的情况 。
方法3
vector<string> findRepeatedDnaSequences(string s) {
unordered_map<int, int> m;
vector<string> r;
int t = 0, i = 0, ss = s.size();
int len = s.size(),hashNum = 0;
if (ss < 11) return r;
while (i < 9)
t = t << 3 | s[i++] & 7;
while (i < ss)
if (m[t = t << 3 & 0x3FFFFFFF | s[i++] & 7]++ == 1)
r.push_back(s.substr(i - 10, 10));
return r;
}
方法3的变种
vector<string> findRepeatedDnaSequences(string s) {
char hashMap[1048576] = {0};
vector<string> ans;
int len = s.size(),hashNum = 0;
if (len < 11) return ans;
for (int i = 0;i < 9;++i)
hashNum = hashNum << 2 | (s[i] - 'A' + 1) % 5;
for (int i = 9;i < len;++i)
if (hashMap[hashNum = (hashNum << 2 | (s[i] - 'A' + 1) % 5) & 0xfffff]++ == 1)
ans.push_back(s.substr(i-9,10));
return ans;
}