Medium
题目描述
DNA序列由一系列核苷酸组成,缩写为’A’、‘C’、‘G’和’T’。
例如,“ACGAATTCCG"是一个DNA序列。
在研究DNA时,识别DNA中的重复序列是很有用的。
给定一个表示DNA序列的字符串s,返回所有在DNA分子中出现超过一次的10个字母长的序列(子字符串)。你可以按任意顺序返回答案。
示例 1:
输入: s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出: ["AAAAACCCCC","CCCCCAAAAA"]
示例 2:
输入: s = "AAAAAAAAAAAAA"
输出: ["AAAAAAAAAA"]
提示:
- 1 <= s.length <= 10^5
- s[i] 是 ‘A’、‘C’、‘G’ 或 ‘T’ 中的一个
解题思路
这道题要求找出DNA序列中所有重复出现的长度为10的子串。
方法一:滑动窗口 + 哈希表 最直观的方法是使用滑动窗口遍历所有长度为10的子串,用哈希表记录每个子串的出现次数。时间复杂度O(n),空间复杂度O(n)。
方法二:位操作优化(推荐) 由于DNA只有4种字符,我们可以用2位二进制表示一个字符(A=0, C=1, G=2, T=3)。这样一个长度为10的子串可以用20位整数表示,大大节省空间。
具体实现:
- 将字符映射为数字:A→0, C→1, G→2, T→3
- 使用滑动窗口,每次移动时去掉最高位,加入新的最低位
- 用哈希集合记录见过的序列和重复的序列
这种方法的优势是空间效率更高,且比较操作更快。
方法三:Rolling Hash 使用多项式滚动哈希,可以在O(1)时间内计算新窗口的哈希值,但需要处理哈希冲突问题。
代码实现
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
if (s.length() <= 10) return {};
unordered_map<char, int> mapping = {{'A', 0}, {'C', 1}, {'G', 2}, {'T', 3}};
unordered_set<int> seen;
unordered_set<int> repeated;
vector<string> result;
int hash = 0;
for (int i = 0; i < 10; i++) {
hash = hash * 4 + mapping[s[i]];
}
seen.insert(hash);
for (int i = 10; i < s.length(); i++) {
hash = hash * 4 + mapping[s[i]];
hash &= (1 << 20) - 1; // 保持20位
if (seen.count(hash) && !repeated.count(hash)) {
repeated.insert(hash);
result.push_back(s.substr(i - 9, 10));
}
seen.insert(hash);
}
return result;
}
};
class Solution:
def findRepeatedDnaSequences(self, s: str) -> List[str]:
if len(s) <= 10:
return []
mapping = {'A': 0, 'C': 1, 'G': 2, 'T': 3}
seen = set()
repeated = set()
result = []
# 计算第一个窗口的哈希值
hash_val = 0
for i in range(10):
hash_val = hash_val * 4 + mapping[s[i]]
seen.add(hash_val)
# 滑动窗口
for i in range(10, len(s)):
hash_val = hash_val * 4 + mapping[s[i]]
hash_val &= (1 << 20) - 1 # 保持20位
if hash_val in seen and hash_val not in repeated:
repeated.add(hash_val)
result.append(s[i-9:i+1])
seen.add(hash_val)
return result
public class Solution {
public IList<string> FindRepeatedDnaSequences(string s) {
if (s.Length <= 10) return new List<string>();
var mapping = new Dictionary<char, int> {
{'A', 0}, {'C', 1}, {'G', 2}, {'T', 3}
};
var seen = new HashSet<int>();
var repeated = new HashSet<int>();
var result = new List<string>();
int hash = 0;
for (int i = 0; i < 10; i++) {
hash = hash * 4 + mapping[s[i]];
}
seen.Add(hash);
for (int i = 10; i < s.Length; i++) {
hash = hash * 4 + mapping[s[i]];
hash &= (1 << 20) - 1; // 保持20位
if (seen.Contains(hash) && !repeated.Contains(hash)) {
repeated.Add(hash);
result.Add(s.Substring(i - 9, 10));
}
seen.Add(hash);
}
return result;
}
}
var findRepeatedDnaSequences = function(s) {
if (s.length <= 10) return [];
const mapping = new Map([['A', 0], ['C', 1], ['G', 2], ['T', 3]]);
const seen = new Set();
const repeated = new Set();
const result = [];
let hash = 0;
for (let i = 0; i < 10; i++) {
hash = hash * 4 + mapping.get(s[i]);
}
seen.add(hash);
for (let i = 10; i < s.length; i++) {
hash = hash * 4 + mapping.get(s[i]);
hash &= (1 << 20) - 1; // 保持20位
if (seen.has(hash) && !repeated.has(hash)) {
repeated.add(hash);
result.push(s.substring(i - 9, i + 1));
}
seen.add(hash);
}
return result;
};
复杂度分析
| 复杂度类型 | 值 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(n) |
其中 n 是字符串 s 的长度。使用位操作优化后,每个子串只需要一个整数存储,相比直接存储字符串更节省空间。