import java.util.Scanner; public class Main { public static int calDistance(String str1, String str2){ int m = str1.length(); int n = str2.length(); int[][] dp = new int[m + 1][n + 1]; for (int i = 0; i <= m; i++) { dp[i][0] = i; } for (int i = 0; i <= n; i++) { dp[0][i] = i; } for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if(str1.charAt(i - 1) == str2.charAt(j - 1)){ dp[i][j] = dp[i - 1][j - 1]; } else dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1; } } return dp[m][n]; } public static void main(String[] args) { Scanner in = new Scanner(System.in); String s1 = in.nextLine(); String s2 = in.nextLine(); System.out.println(calDistance(s1, s2)); } }
Levenshtein Distance (编辑距离) 算法笔记
1. 基本概念
编辑距离是指两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。允许的编辑操作包括:
- 插入一个字符
- 删除一个字符
- 替换一个字符
2. 动态规划解法
2.1 定义状态
使用二维数组 dp
,其中:
dp[i][j]
表示字符串A
的前i
个字符和字符串B
的前j
个字符之间的编辑距离
2.2 初始化
// 当字符串B为空时 for(int i=0; i<=m; i++) dp[i][0] = i; // 需要删除i个字符 // 当字符串A为空时 for(int j=0; j<=n; j++) dp[0][j] = j; // 需要插入j个字符
2.3 状态转移方程
if(A.charAt(i-1) == B.charAt(j-1)) { dp[i][j] = dp[i-1][j-1]; // 字符相同,无需操作 } else { dp[i][j] = min( dp[i-1][j] + 1, // 删除A[i] dp[i][j-1] + 1, // 在A中插入B[j] dp[i-1][j-1] + 1 // 替换A[i]为B[j] ); }
2.4 图解示例
Levenshtein Distance 计算示例:kitten → sitting
动态规划表格
计算过程解析
- 初始化:第一行:空字符串 → "sitting" 需要 7 次插入操作第一列:"kitten" → 空字符串需要 6 次删除操作
- 填充规则:当字符匹配时:dp[i][j] = dp[i-1][j-1]当字符不匹配时:dp[i][j] = min(左+1, 上+1, 左上+1)
- 关键计算点:dp[1][1] (k→s): min(0+1, 1+1, 1+1) = 1 (替换)dp[2][2] (i→i): = dp[1][1] = 1 (相同)dp[5][5] (e→i): min(2+1, 2+1, 3+1) = 3 → 实际取 2 (为什么?)
最优编辑路径
从右下角 dp[6][7]=3
回溯:
- g≠n → 替换(3←4)
- n=n → 保持(4←3)
- i≠e → 替换(3←2)
- t=t → 保持(2←1)
- t=t → 保持(1←1)
- i≠k → 替换(1←0)
实际编辑操作
- k → s (替换)
- 保留 i
- 保留 t
- 保留 t
- e → i (替换)
- 保留 n
- 插入 g
总编辑距离:3 次操作
- 2 次替换(k→s, e→i)
- 1 次插入(末尾加g)
复杂度验证
对于 m=6 (kitten), n=7 (sitting):
- 时间复杂度:6×7=42 次计算
- 空间复杂度:6×7=42 个存储单元