「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」
DNA序列 由一系列核苷酸组成,缩写为 'A', 'C', 'G' 和 'T'.。
- 例如,
"ACGAATTCCG"是一个 DNA序列 。
在研究 DNA 时,识别 DNA 中的重复序列非常有用。
给定一个表示 DNA序列 的字符串 s ,返回所有在 DNA 分子中出现不止一次的 长度为 10 的序列(子字符串)。你可以按 任意顺序 返回答案。
示例 1:
输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出:["AAAAACCCCC","CCCCCAAAAA"]输入:s = "AAAAAAAAAAAAA"
输出:["AAAAAAAAAA"]
示例 2:
输入:nums = [5,6,7,8,9], x = 4
输出:-1
哈希表
我们可以用一个哈希表统计 s 所有长度为 10 的子串的出现次数,返回所有出现次数超过 10 的子串。
代码实现时,可以一边遍历子串一边记录答案,为了不重复记录答案,我们只统计当前出现次数为 2 的子串。
var findRepeatedDnaSequences = function(s) {
const L = 10;
const ans = [];
const cnt = new Map();
const n = s.length;
for (let i = 0; i <= n - L; ++i) {
const sub = s.slice(i, i + L)
cnt.set(sub, (cnt.get(sub) || 0) + 1);
if (cnt.get(sub) === 2) {
ans.push(sub);
}
}
return ans;
};
哈希表 + 滑动窗口 + 位运算
由于 s 中只含有 4 种字符,我们可以将每个字符用 2 个比特表示,即:
- A 表示为二进制 00;
- C 表示为二进制 01;
- G 表示为二进制 10;
- T 表示为二进制 11。
如此一来,一个长为 101010 的字符串就可以用 20 个比特表示,而一个 int 整数有 32 个比特,足够容纳该字符串,因此我们可以将 s 的每个长为 10 的子串用一个 int 整数表示(只用低 20 位)。
注意到上述字符串到整数的映射是一一映射,每个整数都对应着一个唯一的字符串,因此我们可以将方法一中的哈希表改为存储每个长为 10 的子串的整数表示。
如果我们对每个长为 101010 的子串都单独计算其整数表示,那么时间复杂度仍然和方法一一样为 O(NL)。为了优化时间复杂度,我们可以用一个大小固定为 10 的滑动窗口来计算子串的整数表示。设当前滑动窗口对应的整数表示为 x,当我们要计算下一个子串时,就将滑动窗口向右移动一位,此时会有一个新的字符进入窗口,以及窗口最左边的字符离开窗口,这些操作对应的位运算,按计算顺序表示如下:
- 滑动窗口向右移动一位:
x = x << 2,由于每个字符用 2 个比特表示,所以要左移 2 位; - 一个新的字符 ch 进入窗口:,这里 为字符 的对应二进制;
- 窗口最左边的字符离开窗口:x = x & ((1 << 20) - 1),由于我们只考虑 x 的低 20 位比特,需要将其余位置零,即与上 (1 << 20) - 1。
将这三步合并,就可以用 O(1) 的时间计算出下一个子串的整数表示,即 x = ((x << 2) | bin[ch]) & ((1 << 20) - 1)。