题目链接
题目描述
给定一个待测基因片段和一组基因序列数据库,需要找出与待测片段最相似的序列。相似度通过“突变距离”(莱文斯坦距离)来衡量,即一个序列转换为另一个所需的最少单碱基操作(替换、插入、删除)次数。
输入:
- 第一行是一个整数
,代表可接受的最大突变容忍度。
- 第二行是一个整数
,代表基因数据库中序列的总数。
- 接下来
行,每行是一个已知的基因序列。
- 最后一行是待测的基因片段。
输出:
- 精确匹配:如果存在与待测片段完全相同的序列,直接输出该序列。
- 模糊匹配:如果没有精确匹配,则输出所有突变距离小于等于
的序列。结果需先按突变距离从小到大排序,距离相同时按字典序排序,用空格隔开。
- 无匹配:如果上述两种情况都不满足,则输出
None
。
解题思路
本题的核心是计算两个字符串之间的莱文斯坦距离,并根据距离进行分类输出。莱文斯坦距离是动态规划的经典应用。
1. 莱文斯坦距离计算
我们可以定义一个二维 DP 数组 ,表示第一个字符串的前
个字符
与第二个字符串的前
个字符
之间的莱文斯坦距离。
- 状态定义:
是
的前
个字符和
的前
个字符之间的最小突变距离。
- 基本情况:
:将长度为
的字符串转换为空字符串需要
次删除操作。
:将空字符串转换为长度为
的字符串需要
次插入操作。
- 状态转移方程:
对于
,我们考虑
和
:
- 如果
,那么这两个字符匹配,不需要额外操作,距离继承自前一个状态:
- 如果
,我们需要进行一次操作。操作有三种可能:
- 替换: 将
替换为
。操作数 =
- 删除: 删除
。操作数 =
- 插入: 在
后插入
。操作数 =
我们取这三种操作中成本最小的一个:
- 替换: 将
- 如果
最终, 和
的莱文斯坦距离就是
。
2. 整体逻辑
- 读取输入
,
, 数据库序列和待测序列
。
- 遍历数据库中的每个序列
:
- 首先检查是否存在
的精确匹配。如果找到,立即停止遍历,直接输出
并结束程序。
- 首先检查是否存在
- 如果遍历完整个数据库都没有找到精确匹配,则进入模糊匹配逻辑。
- 创建一个列表
用于存储满足条件的序列和它们的距离。
- 再次遍历数据库中的每个序列
:
- 计算
和
之间的莱文斯坦距离
。
- 如果
,则将 (
,
) 这个组合存入
。
- 计算
- 遍历结束后,检查
列表:
- 如果列表为空,说明没有找到任何模糊匹配,输出
None
。 - 如果列表不为空,对其进行排序。排序规则为:首先按距离
升序,如果距离相同,则按序列
的字典序升序。
- 将排序后的序列用空格连接并输出。
- 如果列表为空,说明没有找到任何模糊匹配,输出
代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
using namespace std;
// 计算莱文斯坦距离
int levenshtein_distance(const string& s1, const string& s2) {
int m = s1.length();
int n = s2.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
for (int i = 0; i <= m; ++i) {
dp[i][0] = i;
}
for (int j = 0; j <= n; ++j) {
dp[0][j] = j;
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
dp[i][j] = min({dp[i - 1][j] + 1, // 删除
dp[i][j - 1] + 1, // 插入
dp[i - 1][j - 1] + cost}); // 替换
}
}
return dp[m][n];
}
struct Match {
string seq;
int dist;
};
bool compareMatches(const Match& a, const Match& b) {
if (a.dist != b.dist) {
return a.dist < b.dist;
}
return a.seq < b.seq;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int m, n;
cin >> m >> n;
vector<string> db(n);
for (int i = 0; i < n; ++i) {
cin >> db[i];
}
string target;
cin >> target;
// 检查精确匹配
for (const auto& seq : db) {
if (seq == target) {
cout << target << "\n";
return 0;
}
}
// 模糊匹配
vector<Match> fuzzy_matches;
for (const auto& seq : db) {
int dist = levenshtein_distance(seq, target);
if (dist <= m) {
fuzzy_matches.push_back({seq, dist});
}
}
if (fuzzy_matches.empty()) {
cout << "None\n";
} else {
sort(fuzzy_matches.begin(), fuzzy_matches.end(), compareMatches);
for (size_t i = 0; i < fuzzy_matches.size(); ++i) {
cout << fuzzy_matches[i].seq << (i == fuzzy_matches.size() - 1 ? "" : " ");
}
cout << "\n";
}
return 0;
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
class Match implements Comparable<Match> {
String seq;
int dist;
public Match(String seq, int dist) {
this.seq = seq;
this.dist = dist;
}
@Override
public int compareTo(Match other) {
if (this.dist != other.dist) {
return Integer.compare(this.dist, other.dist);
}
return this.seq.compareTo(other.seq);
}
}
public class Main {
// 计算莱文斯坦距离
private static int levenshteinDistance(String s1, String s2) {
int m = s1.length();
int n = s2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= n; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
int cost = (s1.charAt(i - 1) == s2.charAt(j - 1)) ? 0 : 1;
dp[i][j] = Math.min(dp[i - 1][j - 1] + cost, // 替换
Math.min(dp[i - 1][j] + 1, // 删除
dp[i][j - 1] + 1)); // 插入
}
}
return dp[m][n];
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
int n = sc.nextInt();
sc.nextLine(); // consume newline
List<String> db = new ArrayList<>();
for (int i = 0; i < n; i++) {
db.add(sc.nextLine());
}
String target = sc.nextLine();
// 检查精确匹配
for (String seq : db) {
if (seq.equals(target)) {
System.out.println(target);
return;
}
}
// 模糊匹配
List<Match> fuzzyMatches = new ArrayList<>();
for (String seq : db) {
int dist = levenshteinDistance(seq, target);
if (dist <= m) {
fuzzyMatches.add(new Match(seq, dist));
}
}
if (fuzzyMatches.isEmpty()) {
System.out.println("None");
} else {
Collections.sort(fuzzyMatches);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < fuzzyMatches.size(); i++) {
sb.append(fuzzyMatches.get(i).seq);
if (i < fuzzyMatches.size() - 1) {
sb.append(" ");
}
}
System.out.println(sb.toString());
}
}
}
def levenshtein_distance(s1, s2):
m, n = len(s1), len(s2)
# 初始化DP表
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
dp[i][0] = i
for j in range(n + 1):
dp[0][j] = j
# 填充DP表
for i in range(1, m + 1):
for j in range(1, n + 1):
cost = 0 if s1[i - 1] == s2[j - 1] else 1
dp[i][j] = min(dp[i - 1][j] + 1, # 删除
dp[i][j - 1] + 1, # 插入
dp[i - 1][j - 1] + cost) # 替换
return dp[m][n]
# 读取输入
m = int(input())
n = int(input())
db = [input() for _ in range(n)]
target = input()
# 检查精确匹配
exact_match_found = False
for seq in db:
if seq == target:
print(target)
exact_match_found = True
break
if not exact_match_found:
# 模糊匹配
fuzzy_matches = []
for seq in db:
dist = levenshtein_distance(seq, target)
if dist <= m:
fuzzy_matches.append((dist, seq))
if not fuzzy_matches:
print("None")
else:
# 排序:先按距离,再按字典序
fuzzy_matches.sort()
# 提取排序后的序列
result = [seq for dist, seq in fuzzy_matches]
print(" ".join(result))
算法及复杂度
- 算法:动态规划
- 时间复杂度:
,其中
是数据库中序列的数量,
是数据库中序列的最大长度,
是待测序列的长度。我们需要对
个序列中的每一个都计算一次莱文斯坦距离,而单次计算的复杂度是两个字符串长度的乘积。
- 空间复杂度:
,主要开销来自于计算莱文斯坦距离时使用的二维
表。