持续创作,加速成长!这是我参与「掘金日新计划 · 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 <= 105s[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;
算法步骤:
- s长度不足10, 直接返回[]
if(s.length <= 10) return [] - 计算从
s[0]~s[9]共10个字符组成的子串对应的4进制的和 =>sum, 出现1次, 写入 map,map.set(sum, 1) - 从
s[1]遍历到s[s.length - 10], 计算10个字符组成的子串对应的和, 并且和map中的sum进行对比,仅当当前sum在map中只出现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;