返回

利用动态规划求解字符串编辑距离

闲谈

编辑距离(Edit Distance),这里指的是Levenshtein距离,也就是字符串S1通过插入、修改、删除三种操作最少能变换成字符串S2的次数。接下来介绍利用动态规划来求解字符串的编辑距离。

定义: s_1s_2表示两字符串,dist(i, j)表示字符串s_1[1..i]s_2[1..j]的编辑距离。

状态转移方程:

dist(i, j) = min {
    dist(i - 1, j) + 1, // 删除$s_1$的$i$个字符
    dist(i, j - 1) + 1, // 插入$s_2$的$j$个字符
    dist(i - 1, j - 1) + [s_1(i) != s_2(j)], // 替换$s_1$的$i$个字符为$s_2$的$j$个字符
}

初始条件:

dist(0, 0) = 0

算法步骤:

  1. 初始化一个二维数组dist,其中dist(i, 0) = idist(0, j) = j
  2. 按照状态转移方程计算dist(i, j)
  3. 返回dist(m, n),其中mn分别是s_1s_2的长度。

复杂度分析:

时间复杂度:O(mn),其中mn分别是s_1s_2的长度。

空间复杂度:O(mn),其中mn分别是s_1s_2的长度。

代码实现:

def edit_distance(s1, s2):
    """
    计算两个字符串的编辑距离。

    Args:
        s1: 第一个字符串。
        s2: 第二个字符串。

    Returns:
        编辑距离。
    """

    m, n = len(s1), len(s2)
    dist = [[0 for _ in range(n + 1)] for _ in range(m + 1)]

    # 初始化。
    for i in range(m + 1):
        dist[i][0] = i
    for j in range(n + 1):
        dist[0][j] = j

    # 计算编辑距离。
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            dist[i][j] = min(
                dist[i - 1][j] + 1,  # 删除s1的第i个字符
                dist[i][j - 1] + 1,  # 插入s2的第j个字符
                dist[i - 1][j - 1] + (s1[i - 1] != s2[j - 1]),  # 替换s1的第i个字符为s2的第j个字符
            )

    return dist[m][n]


if __name__ == "__main__":
    s1 = "kitten"
    s2 = "sitting"
    print(edit_distance(s1, s2))  # 3

应用:

编辑距离在自然语言处理、语音识别、拼写检查、机器翻译等领域都有广泛的应用。