1. 程式人生 > >LeetCode--187. Repeated DNA Sequences

LeetCode--187. Repeated DNA Sequences

題目連結:https://leetcode.com/problems/repeated-dna-sequences/

要求尋找長度為10的DNA重複子字串

思路一:這裡可以考慮一個HashMap來儲存出現的子字串及其出現次數,出現第二次的則加入最終答案中,而首次出現的就加入Hashmap中,三次及三次以上出現的不加入只是更新出現次數。思路比較樸素,程式碼如下:

class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
        LinkedList<String> ret=new LinkedList<String>();
        HashMap<String,Integer> hs=new HashMap<String,Integer>();
        for(int i=0;i<s.length()-9;i++)
        {
                int j=i+9;
                String str=s.substring(i,j+1);
                if(hs.containsKey(str))
                {
                    int frequency=hs.get(str);
                    if(frequency==1)
                        ret.add(str);
                    hs.put(str,frequency+1);
                }
                else
                {
                    hs.put(str,1);
                }
        }
        return ret;
    }
}

時間複雜度:O(10m)=O(m)

空間複雜度:O(m)

這個解法效率也很一般。

思路二:總體方法就是來判重。上述思路可以再進一步簡化,可以用一個HashSet來儲存所有已經出現的子字串,然後將重複出現的子字串存到另一個HashSet中,這樣就不用管它到底第幾次出現了,因為重複出現的子字串是無法插入集合中的,程式碼如下:

class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
        HashSet<String> seen = new HashSet<>();
        HashSet<String> reap = new HashSet<>();
        for(int i=0; i<s.length()-9;i++) {
            String temp = s.substring(i,i+10);
            if(!seen.add(temp)) {
                reap.add(temp);
            }
        }
        
        return new ArrayList(reap);
    }
 }

思路三:上面的思路本質都是在雜湊函式,而雜湊函式可以利用rolling hash的方法來減少計算雜湊值的複雜度,這裡具體參考這篇演算法詳解https://blog.csdn.net/To_be_to_thought/article/details/85038546,這裡不展示具體程式碼了。

思路四:因為只有四個字母的情況,我們可以考慮將這四個字母來重現編碼,以實現只有四個字母組成的字母表。觀察發現:

ASCII碼是一個位元組表示的字元表,用0-255的十進位制數來表示字元
         A的二進位制編碼:0100 0001
         C的二進位制編碼:0100 0011
         G的二進位制編碼:0100 0111
         T的二進位制編碼:0101 0100
從編碼的角度看後三位就可以分別表示這四個字元了,也就是說連續10個字元的資訊儲存需要30個bit位,而int型是四個位元組32個bit,足以儲存連續10個字母的編碼資訊。編碼資訊的儲存是取整型數的二進位制表達的後30位置,需要一個掩模0x3fffffff,並且取每個字元的後三位需要一個掩模十進位制7(二進位制0111)

程式碼如下:

class Solution {
    
    public List<String> findRepeatedDnaSequences( String s)
    {
        LinkedList<String> ret=new LinkedList<>();
        if(s==null || s.length()<=9)
            return ret;
        int hash=0;

        HashMap<Integer,Integer> map=new HashMap<>();

        int mask=0x3FFFFFFF;
        for(int i=0;i<9;i++)
            hash=(hash<<3) | (s.charAt(i) & 7);
        map.put(hash,1);
        for(int i=9;i<s.length();i++)
        {
            hash= (hash<<3) & mask | ( s.charAt(i) & 7);
            if(map.containsKey(hash))
            {
                int p=map.get(hash);
                if(p==1)
                    ret.add(s.substring(i-9,i+1));
                map.put(hash,++p);
            }
            else
                map.put(hash,1);
        }
        return ret;
    }
}

我後來看了最高效率的解法也是基於思路三的再編碼思想來做的,只不過四個字母的字母表只需要4個整數(0-3)來編號(對映),也就是先將這四個字母對映成0,1,2,3,這樣原來的30位編碼表達變成了20位編碼表達,掩模也換成了0xfffff(二進位制表達為00000000 00001111 11111111 11111111),程式碼如下:

class Solution {
    
    public List<String> findRepeatedDnaSequences( String s){
        
        List<String> result = new ArrayList();
        if (s == null || s.length() < 10)
            return result;
        int[] map = new int[26];
        map['A'-'A'] = 0;
        map['C'-'A'] = 1;
        map['G'-'A'] = 2;
        map['T'-'A'] = 3;
        int mask = 0xfffff;
        int hash = 1;
        for( int i= 0; i < 9; i ++ )
        {
            hash = (hash << 2 ) | map[s.charAt(i)-'A'];
        }
        byte[] set = new byte[1<<20];
        for( int i = 9; i < s.length(); i ++ )
        {
            hash = ((hash << 2) & mask ) | map[s.charAt(i)-'A'];
            if( set[hash] == 1 )
            {
                result.add( s.substring( i-9, i + 1));
            }
            if( set[hash] < 2)
            {
                set[hash] ++;
            }
        }
        return result;
    }
}