题目链接: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;
}
}