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.

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"].
算法一:

基本思路,从坐向右依次比较。

public class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
        HashSet<String>  set = new HashSet<String>(); //set可以除去重复的序列
        for(int i = 0 ; i < s.length() - 20 ; i ++ ){
            String tmp = s.substring(i,i+10);
            if(s.indexOf(tmp,i+10) > -1){//从i+10之后搜索是否存在tmp
                set.add(tmp);
            }
        }
        List<String> list = new ArrayList<String>(set);
        return list;
    }
}

Submission Result: Time Limit Exceeded

No,No,No,,,,,,,,,,,,,,,,,,,

显然String类中的indexOf方法没有使用类似KMP这样的匹配方法,而是使用了暴力破解。

该怎么提高效率呢?

算法二:

使用KMP,BM等匹配算法来提高效率

但是,尽管在一次遍历比较中效率提高了,但是我们依然需要进行n-10次遍历,

这种向后看的缺点就是我们看不到未来,因此需要对每一个可能的10-long-sequence都向后匹配一次,查询它是否是重复的,所以KMP和BM并不是很适合这种情况。

算法三:

前两种算法都是向后看,也就是从左向右进行。

接下来我们换种思路,我们采用向前看的思路。

就是我们检查一个10-long-sequence是否在以前出现过,需要消耗一定空间(如果一个字符一个字节,那么需要O(n2));我们是否可以减小消耗空间?

注意到整个序列中只有4种字符A,C,G,T.

而且他们各自的ASCII码是

A: 0100 0001  C: 0100 0011  G: 0100 0111  T: 0101 0100

只有后三位不同,而一个int型是32位,所以10-long-sequence只需要一个4个字节就可以存储了。

而将十个字符组成一个整数,才用的是<<和&,如:cur = (cur << 3) | (s.charAt(index) & 0x07)

当要结合第一到十一个字符时,先提取出后27位,再<<3,然后将第十一个字符加到尾部,如:cur = ((cur & 0x07ffffff) << 3) | (s.charAt(index++) & 0x07);

既然要向前看,那么我们就需要保存遍历前的子序列,故需要一个map。

参考自http://www.cnblogs.com/grandyang/p/4284205.html(这是C++版的)

public class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
        Map<Integer,Integer> map = new HashMap<Integer,Integer>();
        List<String> list = new ArrayList<String>();
        if(s.length() < 10 ){
            return list;
        }
        int index = 0;
        int cur = 0;//用来存储10-long-sequence
        for( ; index < 9; index ++){//提取出了前九个字符
            cur = (cur << 3) | (s.charAt(index) & 0x07);
        }
        while(index < s.length()){
            cur = ((cur & 0x07ffffff) << 3) | (s.charAt(index++) & 0x07);//提取10-long-sequence的第十个字符;
            if(map.containsKey(cur)){//前面是否存在cur这个序列
                int count = map.get(cur);
                if(count == 1){//cur在前面仅出现了一次
                    list.add(s.substring(index - 10,index));//如果存在说明这个序列出现过,则加入list
                }
                map.put(cur,++count);
            }else{//不存在,则添加一个新的键值对
                map.put(cur,1);
            }
        }
        return list;
    }
}

Runtime: 409 ms

这个题要注意到的是只有四个字符,如果有26个字符呢?显然也就不合适来(7*26bit)

所以又有人提出了将四个字符编码,A :00; C:01; G:10; T:11

20bit就可以表示10-long-sequence,看着仿佛减少了存储空间,实际上并没有,还是用int来保存(我还是认为上面的算法,至少不用自己管字符和编码之间的转换)

接下来的思路还是向前看的思路。

你可能感兴趣的:(LeetCode,dna,repeated,Sequenc)