题目大意是DNA序列由‘ACGT’4个字符自由组合而成,现在给定一个这种序列字符串,要求判断10个字符串长的连续字串出现次数超过一次的所有情况。
Given s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT",
Return:
["AAAAACCCCC", "CCCCCAAAAA"].
思路:
这道题最原始的思路就是从前往后扫描一遍判断每个长度为10的字符串是否在hash表中已经存在了,如果存在了说明是有重复的,如果是第二次加入hash表(hash表需要记录已经扫描到了这个字符串多少次)则将这个字符串加入到结果中。如果没有在hash表中出现的话,那么将该字符串加入到hash表中。得到一般的思路后就是来优化该现有的思路。该题无论输入字符串怎么组合,基本的元只有四种即ACGT,对应ASCII码:
A: 0100 0001 C: 0100 0011 G: 0100 0111 T: 0101 0100
我们的目的是利用位来区分字符,当然是越少位越好,通过观察发现,每个字符的后三位都不相同,故而我们可以用末尾三位来区分这四个字符。而题目要求是10个字符长度的串,每个字符用三位来区分,10个字符需要30位,在32位机上也OK。为了提取出后30位,我们还需要用个mask,取值为0x7ffffff,用此mask可取出后27位,再向左平移三位即可。
我们先取前9个字符,每个只取后3位,为了构成连续27位,我们在每个字符取代后3位后向左移动3位。这种机制也保证了每次扫描一个字符后都是该字符及该字符之前10个字符构成的10字符长的新字串进行HASH里的比较。例如,首先我们取出前九个字符AAAAACCCC,根据上面的分析,我们用三位来表示一个字符,所以这九个字符可以用二进制表示为001001001001011011011,然后我们继续遍历字符串,下一个进来的是C,则当前字符为AAAAACCCCC,二进制表示为001001001001011011011011,然后我们将其存入哈希表中,用二进制的好处是可以用一个int变量来表示任意十个字符序列,比起直接存入字符串大大的节省了内存空间,然后再读入下一个字符C,则此时字符串为AAAACCCCCA,我们还是存入其二进制的表示形式,以此类推,当某个序列之前已经出现过了,我们将其存入结果res中即可
代码:
vector<string> findRepeatedDnaSequences(string s) {
vector<string> res;
if(s.size()<=10)return res;
int flag=0x7ffffff;
map<int,int> m;
int i;
int cur=0;
for(i=0;i<9;i++)
cur=(cur<<3)|(s[i]&7);//获得前9个字符的3bit位
for(i=9;i<s.size();i++)
{
cur=((cur&flag)<<3)|(s[i]&7);//取第10位
if(m.find(cur)!=m.end()){
if(m[cur]==1)res.push_back(s.substr(i-9,10));//超过一次
++m[cur];
}//该字串有
else {m[cur]=1;}//字符串第一次出现
}
return res;
}