[leetcode] Repeated DNA Sequences

题目链接https://leetcode.com/problems/repeated-dna-sequences/

题意:寻找出现1次以上的长度为10的基因片段,例如:

需要特别注意的是:所有基因均由A, C, G, T 四种核苷酸组成。


看到题目,最先想到的是提取所有的长度为10的片段,放入map中,map的key为长度为10的片段,value为key出现的起始位置。提交之后果断超内存Memory Limit Exceeded了。

后来看到题目的标签为Bit Manipulation ,而且字符串中只有4种字母,1个bit可以表示2个字母的话,2个bit就可以表示4个字母,我们可以用00 (0), 01 (1), 10 (2), 11 (3)分别表示 A, C, G, T,一个片段有10个字母,只需要2*10 = 20个bit即可表示一个片段,即一个int即可。于是可把string key化为int key。代码如下:

class Solution {
private:
    int str2i(string s){
        int ret = 0;
        for(int i=0; i<10; ++i){
            int b = 2*i;
            switch(s[i]){
                case 'A':
                    ret |= (0<<b);
                    break;
                case 'C':
                    ret |= (1<<b);
                    break;
                case 'G':
                    ret |= (2<<b);
                    break;
                case 'T':
                    ret |= (3<<b);
                    break;
            }
        }
        return ret;
    }
    
public:
    // 00 A, 01 C, 10 G, 11 T
    vector<string> findRepeatedDnaSequences(string s) {
        vector<string> ret;
        int n = s.size();
        unordered_map<int, vector<int>> mp;
        string sub = s.substr(0,10);
        mp[str2i(sub)].push_back(0);
        for(int i=10; i<n; ++i){
            sub = sub.substr(1);
            sub.push_back(s[i]);
            int k = str2i(sub);
            mp[k].push_back(i-10+1);
        }
        unordered_map<int, vector<int>>::iterator it;
        for(it = mp.begin(); it != mp.end(); ++ it){
            if(it->second.size()>1){ 
                ret.push_back(i2str(it->first));
            }
        }
        return ret;
    }
};
这里其实无需保存每个片段的起始位置,只保存其出现的次数即可,稍作优化后的代码如下:

class Solution {
private:
    int str2i(const string &s, int pos){
        int ret = 0;
        for(int i=pos; i<pos+10; ++i){
            int b = 2*(i-pos);
            switch(s[i]){
                case 'A':
                    ret |= (0<<b);
                    break;
                case 'C':
                    ret |= (1<<b);
                    break;
                case 'G':
                    ret |= (2<<b);
                    break;
                case 'T':
                    ret |= (3<<b);
                    break;
            }
        }
        return ret;
    }
public:
   // 00 A, 01 C, 10 G, 11 T
    vector<string> findRepeatedDnaSequences(string s) {
        vector<string> ret;
        int n = s.size();
        unordered_map<int, int> mp;
        for(int i=0; i<=n-10; ++i){
            int k = str2i(s,i);
            ++ mp[k];
            if(mp[k] == 2){
                ret.push_back(s.substr(i,10));
            }
        }
        return ret;
    }
};

最近在二刷,重新做时又发现两处可改进的地方,其中第一处是提速的关键:

(1) 由于我们在扫描长度为10的片段时,总是连续进行的,即:移除开头的字符,并加入后续的一个字符,很明显,在计算这两个连续的10长片段的哈希值时,中间的9个字符都被重复计算了。其实,我们可以利用上一个片段的哈希值,来计算下一个片段的哈希值,例如AAAAACCCCCA,如果我们已知AAAAACCCCC的哈希值,我们只需将开头的A所在的2位归0,并将剩余的数位左移2位,然后加上后面的A对应的哈希码,即为片段AAAACCCCCA对应的哈希值。

(2) 由于片段长度固定为10,其中每一个字符可用两个bit来表示,即总共所需20bit,数值范围为0~2^20(1048576),其实可以开一个大小为1048576的数组进行计数,避免了unordered_map的开销。

代码如下:

class Solution {
public:
    int getHashCode(string s, int pos){
        int ret = 0;
        for(int i=pos; i<pos+10; ++i){
            switch(s[i]){
                case 'A':
                    ret = (ret<<2)|0;
                    break;
                case 'C':
                    ret = (ret<<2)|1;
                    break;
                case 'G':
                    ret = (ret<<2)|2;
                    break;
                case 'T':
                    ret = (ret<<2)|3;
                    break;
            }
        }
        return ret;
    }
    
    int getBits(char c){
        int ret = 0;
        switch(c){
                case 'A':
                    ret = 0;
                    break;
                case 'C':
                    ret = 1;
                    break;
                case 'G':
                    ret = 2;
                    break;
                case 'T':
                    ret = 3;
                    break;
        }
        return ret;
    }

    vector<string> findRepeatedDnaSequences(string s) {
        vector<int> st(1048576 ,0);
        vector<string> ret;
        if(s.size() < 11)  return ret; 
        int tmp = getHashCode(s,0);
        st[tmp] = 1;
        for(int i=10; i<s.size(); ++i){
            tmp = ((tmp & 0xfff3ffff) << 2)|(getBits(s[i]));
            if(st[tmp] == 0){
                st[tmp] = 1;
            }else{
                if(st[tmp] == 1){
                    ret.push_back(s.substr(i-9,10));
                }
                ++ st[tmp];
            }
        }
        return ret;
    }
};


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值