问题描述
DNA 是由 A、C、G、T 四种核苷酸组成,例如AAAGTCTGAC,假定自然环境下 DNA 发生异变的情况有:
- 基因缺失一个核苷酸
- 基因新增一个核苷酸
- 基因替换一个核苷酸
且发生概率相同。
古生物学家 Sam 得到了若干条相似 DNA 序列,Sam 认为一个 DNA 序列向另外一个 DNA 序列转变所需的最小异变情况数可以代表其物种血缘相近程度,异变情况数越少,血缘越相近,请帮助 Sam 实现获取两条 DNA 序列的最小异变情况数的算法。
输入格式
每个样例只有一行,两个 DNA 序列字符串以英文逗号“,”分割
输出格式
输出转变所需的最少情况数,类型是数字
输入样例:
- AGT,AGCT
输出样例:
- 1
数据范围:
每个 DNA 序列不超过 100 个字符
解决思路:
这道题本质上实际是最短编辑距离类的问题。
最短编辑距离(Edit Distance)问题是计算将一个字符串转换为另一个字符串所需的最小操作数。允许的操作通常包括:
- 插入一个字符。
- 删除一个字符。
- 替换一个字符。
举个例子: 给定字符串 s1 = "kitten" 和 s2 = "sitting",我们需要计算将 kitten 转换为 sitting 的最小操作次数。
为什么说将一个字符串A转换为另一个字符串B只有三种操作呢?这是因为对一个字符串A的插入操作与对另一个字符串B的删除操作是等价的,同理,二者的替换操作也是等价的,所以我们只需要考虑这三种情况就可以了。 解决最短编辑距离问题的核心是使用动态规划。我们构建一个二维数组 dp,其中 dp[i][j] 表示将 s1 的前 i 个字符转换为 s2 的前 j 个字符所需的最小编辑距离。
状态转移
- 初始化:
-
- 当 s1 或 s2 为空时,编辑距离等于另一个字符串的长度。
- dp[0][0] = 0(两个空字符串的距离为0)。
- dp[i][0] = i(从 s1 的前 i 个字符到空字符串的距离为 i)。
- dp[0][j] = j(从空字符串到 s2 的前 j 个字符的距离为 j)。
- 填充 dp 数组:
-
- 如果 s1[i-1] == s2[j-1],则不需要额外的操作:
dp[i][j]=dp[i−1][j−1]dp[i][j] = dp[i-1][j-1]dp[i][j]=dp[i−1][j−1]
-
- 如果 s1[i-1] != s2[j-1],我们需要考虑三种操作:
-
- 插入:将一个字符插入到 s1,使得 dp[i][j] = dp[i][j-1] + 1。
- 删除:删除 s1 的一个字符,得到 dp[i][j] = dp[i-1][j] + 1。
- 替换:替换 s1[i-1] 为 s2[j-1],得到 dp[i][j] = dp[i-1][j-1] + 1。
- 综合这三者,取最小值:
复杂度分析
- 时间复杂度:O(m * n),其中 m 和 n 分别是两个字符串的长度。我们需要填充一个大小为 (m+1) x (n+1) 的动态规划表。
- 空间复杂度:O(m * n),存储动态规划表。如果需要优化空间复杂度,可以只使用一维数组来保存当前和上一行的结果。
在最短编辑距离的动态规划解决方案中,每个状态 dp[i][j] 只依赖于当前行 i 和前一行 i-1 的状态。因此,我们并不需要保存整个二维数组,而只需要保存当前行和前一行的结果。
- 使用两行一维数组:
-
- 我们可以使用两个一维数组 prev 和 curr,分别存储 dp[i-1] 和 dp[i] 的值。
- 对于每一行的计算,我们只需要使用当前行的结果和前一行的结果即可。
- 更新规则:
-
- 在计算 dp[i][j] 时,使用 prev[j] 来表示 dp[i-1][j],而 curr[j-1] 来表示 dp[i][j-1]。
- 当我们完成一行的计算后,将 curr 数组的值复制到 prev 数组,以便在下一行的计算中使用。