「前端刷题」187.重复的DNA序列(MEDIUM)

104 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

题目(Repeated DNA Sequences)

链接:https://leetcode-cn.com/problems/repeated-dna-sequences
解决数:765
通过率:52.8%
标签:位运算 哈希表 字符串 滑动窗口 哈希函数 滚动哈希 
相关公司:linkedin google amazon 

DNA序列 由一系列核苷酸组成,缩写为 'A''C''G' 和 'T'.。

  • 例如,"ACGAATTCCG" 是一个 DNA序列 。

在研究 DNA 时,识别 DNA 中的重复序列非常有用。

给定一个表示 DNA序列 的字符串 s ,返回所有在 DNA 分子中出现不止一次的 长度为 10 的序列(子字符串)。你可以按 任意顺序 返回答案。

 

示例 1:

输入: s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出: ["AAAAACCCCC","CCCCCAAAAA"]

示例 2:

输入: s = "AAAAAAAAAAAAA"
输出: ["AAAAAAAAAA"]

 

提示:

  • 0 <= s.length <= 105
  • s[i]``==``'A''C''G' or 'T'

思路

hashMap + 遍历 从下表 i=0 遍历到 i=s.length-10,往 map 中写入每个长度为10的子串和出现次数

var findRepeatedDnaSequences = function (s) {
  const result = [];
  const map = new Map();
  for (let i = 0; i <= s.length - 10; i++) {
    const curS = s.slice(i, i + 10);
    if (map.get(curS) === 1) {
      result.push(curS);
    }
    map.set(curS, map.has(curS) ? map.get(curS) + 1 : 1);
  }
  return result;
}

hashMap + 位操作

把A\C\G\T对应为 4进制 的位, A:0, C:1, G:2, T:3, 那么每一个10位长度的子串都 对应 一个4进制子串表示的数,如果两个数相等,那么两个10位的子串相等[出现超过1次]

子串AAAAACCCCC => 0000011111(4进制), 大小等于10进制下的341;
子串AAAACCCCCA => 0000111110(4进制), 大小等于10进制下的1364;
......
子串CCCCCAAAAA => 1111100000(4进制), 大小等于10进制下的349184;

算法步骤:

  1. s长度不足10, 直接返回[] if(s.length <= 10) return []
  2. 计算从 s[0]~s[9] 共10个字符组成的子串对应的4进制的和 => sum, 出现1次, 写入 map, map.set(sum, 1)
  3. s[1] 遍历到 s[s.length - 10], 计算10个字符组成的子串对应的和, 并且和 map 中的 sum 进行对比,仅当当前 summap 中只出现1次才写入结果数组 result 中。

这里有个技巧。 以 s[i] 开头的10个字符组成的子串(子串之和 {sum[i]}) 对比以 s[i+1] 开头的10个字符组成的子串(子串之和 {sum[i+1]}),有9位都是一样的,仅有1位不一样, 因此可以利于位操作,基于sum[i] 迅速求出 sum[i+1]

我们把 s[i] 对应的4进制数往左移动一位(在2进制下是左移两位, <<2), 那么 s[i] 就可以与 s[i+1] 对齐。又由于子串的4进制一共是10位,那么实际为20位2进制,左移的过程需要削掉左移多出来的位数,需要 0xfffff[一共20个1]按位与[&], 最后再加上第10位字符对应的数字即可。 sum[i+1] = ( (sum[i]<<2) & 0xfffff ) + charMap[s[i]]

s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
子串AAAAACCCCC => 0000011111(4进制), 大小等于10进制下的341;
那么子串AAAACCCCCA => ((341<<2) & 0xffff) + charMap[s[i+9]], 此时i=1, 表示从第1个字符开始, 结果为1364
相应地,
子串AAACCCCCAA => ((1364<<2) & 0xfffff) + charMap[s[i+9]], 此时i=2, 表示从第1个字符开始, 结果为5456
......

完整过程如下:

var findRepeatedDnaSequences = function (s) {
  if (s.length <= 10) return [];
  const charMap = {
    A: 0,
    C: 1,
    G: 2,
    T: 3,
  };

  const result = [];
  const map = new Map();
  let sum = 0;
  for (let i = 0; i < 10; i++) {
    sum = sum * 4 + charMap[s[i]];
  } // 前10个字符组成的子串之和
  map.set(sum, 1);
  for (let i = 1; i <= s.length - 10; i++) {
    // 以s[i]开头的10个字符对应的数值满足[一共20bit,需要与0xfffff相与]
    // sum[i] = ((sum[i-1] << 2) & 0xfffff) + charMap[s[i+9]]
    sum = ((sum << 2) & 0xfffff) + charMap[s[i + 9]];
    if (map.get(sum) === 1) {
      result.push(s.slice(i, i + 10));
    }
    map.set(sum, map.has(sum) ? map.get(sum) + 1 : 1);
  }
  return result;