Repeated DNA Sequences

本文介绍了一种方法,用于在给定的DNA序列中找出所有长度为10的重复子序列。通过使用哈希表和位运算技术,实现高效搜索。代码示例展示了如何实现这一功能。
All DNA is composed of a series of nucleotides abbreviated as A, C, G, and T, for example: "ACGAATTCCG". When studying DNA, it is sometimes useful to identify repeated sequences within the DNA.

Write a function to find all the 10-letter-long sequences (substrings) that occur more than once in a DNA molecule.

For example,

Given s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT",

Return:
["AAAAACCCCC", "CCCCCAAAAA"].

给定一个DNA序列,从中找出长度为10的出现次数大于一次的所有子序列。我们可以借助string的substring方法,从第一个字符开始一次截取,借助哈希表查看是否是重复的子串,如果重复就将这个字符串放入结果集中(结果集中不能重复),对于解决结果集中不能重复我们可以通过contains方法来解决,也可以通过建立两个哈希表,判断一个子串是否应该加入结果集合中时,如果该子串在第一个集合中存在,在第二个集合中不存在,这种情况下就加入,并且这样可以保证结果集中不会有重复的结果。set中的add方法,如果存在要加入的元素返回false,如果不存在当前要加入的元素返回true。代码如下:

public class Solution {
public List<String> findRepeatedDnaSequences(String s) {
List<String> list = new ArrayList<String>();
Set<String> set = new HashSet<String>();
Set<String> secondSet = new HashSet<String>();
if(s == null || s.length() <= 10) return list;
for(int i = 0; i <= s.length() - 10; i++) {
String str = s.substring(i, i + 10);
if(!set.add(str) && secondSet.add(str)) {
list.add(str);
}
}
return list;
}
}


我们还可以通过位运算来处理,DNA序列中只有四个元素,我们用两个比特位就可以表示,在构造子串的时候,设置一个变量,初始值为0,然后每次移动两位添加新的元素,移动十次之后就构成了一个子串,这样我们通过比较这个数是否在集合中出现就可以了。代码如下:

public class Solution {
public List<String> findRepeatedDnaSequences(String s) {
List<String> list = new ArrayList<String>();
Set<Integer> set = new HashSet<Integer>();
Set<Integer> secondSet = new HashSet<Integer>();
if(s == null || s.length() <= 10) return list;
int[] map = new int[26];
map['A' - 'A'] = 0;
map['C' - 'A'] = 1;
map['G' - 'A'] = 2;
map['T' - 'A'] = 3;
for(int i = 0; i <= s.length() - 10; i++) {
int key = 0;
for(int j = i; j < i + 10; j++) {
key |= map[s.charAt(j) - 'A'];
key <<= 2;
}
if(!set.add(key) && secondSet.add(key)) {
list.add(s.substring(i, i + 10));
}
}
return list;
}
}
<think>我们分析用户提供的错误信息,并逐一解决: 错误1: Line 11: Char 25: error: cannot find value `res` in this scope 原因:变量`res`的作用域问题。在循环内部定义的`res`,在循环外部使用?但实际上错误信息显示在循环内部使用时就找不到?这可能是由于循环内部有嵌套作用域?但看代码结构,这个错误可能是由于循环体的大括号没有正确匹配,导致`res`未定义。然而,从提供的代码片段看,循环体是完整的。另一种可能是,在`entry(res)`中,`res`可能被移动了,但这里我们使用的是`res`的所有权,而`res`是`String`类型,在`entry`调用后就不能再使用。但是,错误信息是“not found in this scope”,所以更可能是作用域问题。 错误2: Line 11: Char 13: error: cannot multiply `std::string::String` by `&mut i32` 原因:这行代码原本是:`*hash.entry(res).or_insert(0) += 1;`,这里并没有乘法操作。这个错误信息很奇怪,可能是编译器在解析表达式时产生了歧义。但更可能的原因是,之前的错误导致编译器无法正确解析,从而产生了这个误导性的错误。 错误3: Line 11: Char 43: error: invalid left-hand side of assignment 原因:`+=`操作符的左边必须是可赋值的,这里是一个解引用操作`*...`,但可能因为前面的错误导致解析失败。 错误4: Line 10: Char 34: error: mismatched types 原因:`s[i..10]`,这里索引`i`和`10`应该是`usize`类型,但用户可能使用了`i32`类型的变量。另外,`s`是`String`类型,而`String`类型的索引要求是`usize`,且范围必须是字符边界(因为UTF-8编码)。但这里我们取的是DNA序列,所以字符都是ASCII,因此按字节切片是安全的。但是,如果`i`是`i32`类型,则不能直接用于索引。 错误5: Line 9: Char 9: error: mismatched types 原因:函数应该返回`Vec<String>`,但循环块返回的是`()`,即没有返回值。我们需要在循环后返回结果。 综合以上错误,我们重构代码: 1. 确保循环变量`i`是`usize`类型,且循环范围正确(避免越界)。 2. 在循环中,每次取从`i`开始的10个字符的子串,并转换为字符串。 3. 使用这个子串作为键去更新哈希表(计数)。 4. 循环结束后,遍历哈希表,将计数大于1的序列收集到向量中返回。 注意:原代码中,循环的范围是`0..len`,这会导致当字符串长度小于10时,切片会panic。而且即使长度大于10,最后一个子串的起始索引应该是`len-10`,所以循环应该是`0..=len-10`,但要注意当`len<10`时,应该跳过循环。 修改后的代码: ```rust use std::collections::HashMap; pub fn find_repeated_dna_sequences(s: String) -> Vec<String> { let len = s.len(); // 如果字符串长度小于等于10,则不可能有重复的10长度子串 if len <= 10 { return vec![]; } let mut hash: HashMap<String, i32> = HashMap::new(); // 注意:i的范围是0..=len-10,因为最后一个子串的起始索引为len-10 for i in 0..=len-10 { // 取从i开始的10个字符 let res = s[i..i+10].to_string(); // 更新计数 *hash.entry(res).or_insert(0) += 1; } // 收集所有计数大于1的序列 hash.into_iter() .filter(|(_, count)| *count > 1) .map(|(seq, _)| seq) .collect() } ``` 但是,上面的代码在索引时仍然可能出错,因为`i`是`usize`类型,而`len-10`在`len<10`时会导致下溢(在Rust中,无符号整数下溢会panic)。因此,我们使用条件判断避免这种情况。 我们已经处理了`len<=10`的情况,所以循环中`len-10`不会下溢(因为循环条件是`0..=len-10`,且当`len<=10`时不会进入循环)。 但是,原错误信息中还有类型不匹配的问题,可能是由于循环变量`i`的类型不是`usize`?在Rust中,`for i in 0..len`,`i`是`usize`类型,所以应该没有问题。 另外,原错误信息中有一个关于`res`作用域的问题,修改后的代码中`res`在循环体内定义,在循环体内使用,所以作用域正确。 最后,我们返回了收集的向量,所以返回类型匹配。 注意:原错误信息中有一个关于`String`乘以`&mut i32`的错误,这可能是由于编译器在解析`*hash.entry(res).or_insert(0) += 1;`时产生的歧义,但修改后的代码应该没有这个问题。 但是,我们还需要注意:`s[i..i+10]`要求`s`必须是可索引的,而`String`的索引操作实际上是字节切片。由于DNA序列只包含'A','C','G','T',都是ASCII字符,所以每个字符占一个字节,因此按字节切片是安全的。 如果字符串中包含非ASCII字符,则可能出错,但题目限定为DNA序列,所以安全。 因此,我们提供上述修改后的代码。 但是,用户希望尽可能保持原有结构,所以我们保留了原循环结构,只是调整了循环范围和返回结果。 另外,原代码中`let mut res: String = s[i..10].to_string()`,这里结束索引是10,应该是固定的10个字符,但应该从i开始取10个,所以结束索引应为`i+10`,原代码写成了10,这会导致每次都取从i到10的子串(当i>10时,这个范围是空的,会panic)。所以修改为`s[i..i+10]`。 因此,我们修正了子串的结束索引。 最后,关于错误4:Line 10: Char 34: error: mismatched types,原代码中`let mut res: String = s[i..10].to_string()`,这里结束索引10是`i32`类型?在Rust中,索引必须是`usize`,而10是整数,默认是`i32`,但Rust允许在索引时使用整数,因为会隐式转换为`usize`。所以这个错误可能是由于其他原因?但原错误信息说“expected `String`, found `()`”,这很奇怪,因为`s[i..10].to_string()`返回的是`String`,而`res`的类型是`String`,所以应该匹配。可能的原因是原代码中这一行末尾有分号,导致整个语句是表达式语句,返回`()`,而`let`语句要求赋值,所以这里没有类型不匹配。所以这个错误可能是由于其他错误连带产生的。 综上所述,我们按照上述修改后的代码即可修复。 但是,为了效率,我们也可以考虑使用滑动窗口和哈希来避免频繁创建字符串,比如使用一个固定长度的数组或使用字符串的切片引用,但题目要求返回`Vec<String>`,所以我们需要存储字符串。而且DNA序列长度固定为10,所以创建字符串的开销可以接受。 因此,我们提供上述代码。 注意:如果字符串很长,重复序列很多,使用`HashMap<String, i32>`可能会占用较多内存,可以考虑使用`HashMap<&str, i32>`,但这样需要解决生命周期问题。我们可以通过存储切片,但需要确保原始字符串的生命周期足够长。由于参数`s`是`String`,我们可以在循环中存储切片,但这样要求哈希表的生命周期不能超过`s`,而函数返回时,哈希表需要返回,所以不能使用切片(因为哈希表在函数结束后被销毁,而切片引用自`s`,但返回的向量是新的字符串,所以没有问题)。因此,我们使用`String`是安全的。 修改后的完整代码: ```rust use std::collections::HashMap; pub fn find_repeated_dna_sequences(s: String) -> Vec<String> { let n = s.len(); if n < 10 { return Vec::new(); } let mut map: HashMap<String, i32> = HashMap::new(); for i in 0..=n - 10 { let substr = s[i..i + 10].to_string(); *map.entry(substr).or_insert(0) += 1; } map.into_iter() .filter(|(_, count)| *count > 1) .map(|(seq, _)| seq) .collect() } ``` 这个代码应该可以编译并正确运行。 如果用户希望更高效,可以使用`&str`来避免复制,但这样需要改变函数的签名,因为返回的`&str`必须与输入字符串生命周期相关联。但题目要求函数签名是`(s: String) -> Vec<String>`,所以我们不能改变。 因此,我们按照上述代码提供修复。 最后,关于错误5:函数返回类型不匹配,我们在函数末尾添加了返回语句,所以修复了。 总结:修改了循环范围,修正了子串的结束索引,添加了边界条件判断,并在最后收集结果返回。 希望这能解决用户的问题。</think>根据您提供的错误信息,我分析了代码中的问题并给出修复方案。以下是错误原因及修复方法: ### 错误原因分析 1. **作用域问题**:`res` 变量在循环内部定义,但在 `entry(res)` 使用时可能已被移动(move)而非借用(borrow)[^1] 2. **类型不匹配**: - 索引操作 `s[i..10]` 中 `i` 应为 `usize` 类型而非 `i32` - 字符串切片应转换为 `&str` 而非 `String` 以减少内存分配 3. **边界处理**: - 循环范围 `0..len` 可能导致索引越界 - 未处理字符串长度小于10的情况 4. **返回值缺失**:循环结束后未返回结果集合 ### 修复后的代码 ```rust use std::collections::HashMap; pub fn find_repeated_dna_sequences(s: String) -> Vec<String> { let len = s.len(); if len < 10 { return vec![]; } let mut hash: HashMap<&str, i32> = HashMap::new(); let bytes = s.as_bytes(); // 使用字节切片提高性能 for i in 0..=len - 10 { // 修复1:正确索引范围 // 修复2:使用字节切片避免内存分配 let seq = unsafe { std::str::from_utf8_unchecked(&bytes[i..i + 10]) }; // 修复3:直接更新计数器 *hash.entry(seq).or_insert(0) += 1; } // 修复4:返回结果集合 hash.into_iter() .filter(|(_, count)| *count > 1) .map(|(seq, _)| seq.to_string()) .collect() } ``` ### 关键修复点说明 1. **索引范围修正**: ```rust for i in 0..=len - 10 // 确保索引不越界 ``` 使用 `..=` 包含范围上限,避免 `i + 10` 越界[^2] 2. **高效字符串处理**: ```rust let bytes = s.as_bytes(); let seq = unsafe { std::str::from_utf8_unchecked(&bytes[i..i + 10]) }; ``` - 通过字节切片操作避免内存分配 - `unsafe` 块在DNA序列(仅ACGT)场景安全 3. **所有权处理**: ```rust *hash.entry(seq).or_insert(0) += 1 ``` 直接使用字符串切片 `&str` 避免 `String` 的所有权转移问题 4. **结果收集**: ```rust hash.into_iter() .filter(|(_, count)| *count > 1) .map(|(seq, _)| seq.to_string()) .collect() ``` 使用迭代器管道高效处理结果集 ### 性能优化建议 1. **预分配空间**: ```rust let mut hash = HashMap::with_capacity(len/10); ``` 2. **避免字符串复制**(当允许修改输入时): ```rust let seq = &s[i..i+10]; // 直接借用切片 ``` 3. **使用滚动哈希**(处理超长序列): ```rust // 示例:Rabin-Karp 算法 let mut hash_val = 0; for j in 0..10 { hash_val = (hash_val << 2) | dna_to_bits(bytes[i+j]); } ``` ### 安全边界说明 Rust的所有权系统在此修复中发挥关键作用: 1. 通过借用检查器确保切片访问安全 2. `HashMap` 的entry API避免空值访问 3. 类型系统保证索引计算正确性[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值