def LevenshteinDistance(s1: str, s2: str) -> int: """ 计算两个字符串的 Levenshtein 距离(编辑距离) :param s1: 原始字符串 :param s2: 目标字符串 :return: 最少编辑操作次数(差异程度) """ # 创建二维动态规划表,dp[i][j] 表示 s1[:i] 到 s2[:j] 的编辑距离 m, n = len(s1), len(s2) dp = [[0] * (n + 1) for _ in range(m + 1)] # 初始化:空字符串到 s1[:i] 需要 i 次删除操作 for i in range(m + 1): dp[i][0] = i # 初始化:空字符串到 s2[:j] 需要 j 次插入操作 for j in range(n + 1): dp[0][j] = j # 填充动态规划表 for i in range(1, m + 1): for j in range(1, n + 1): # 字符相同,无需操作 if s1[i-1] == s2[j-1]: dp[i][j] = dp[i-1][j-1] else: # 字符不同,取「删除、插入、替换」中最小操作数 +1 dp[i][j] = min( dp[i-1][j], # 删除 s1[i-1] dp[i][j-1], # 插入 s2[j-1] 到 s1 dp[i-1][j-1] # 替换 s1[i-1] 为 s2[j-1] ) + 1 return dp[m][n] def LongestCommonSubsequence(s1: str, s2: str) -> tuple[list[tuple[int, int]], str]: """ 计算两个字符串的最长公共子序列(LCS),并返回 LCS 字符的位置映射 :param s1: 原始字符串 :param s2: 目标字符串 :return: (LCS 位置列表 [(S1Idx, S2Idx)], LCS 字符串) """ m, n = len(s1), len(s2) # dp[i][j] 表示 s1[:i] 和 s2[:j] 的 LCS 长度 dp = [[0] * (n + 1) for _ in range(m + 1)] # 填充 LCS 长度表 for i in range(1, m + 1): for j in range(1, n + 1): if s1[i-1] == s2[j-1]: dp[i][j] = dp[i-1][j-1] + 1 else: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) # 回溯找 LCS 的具体字符和位置 LcsChars = [] LcsPositions = [] i, j = m, n while i > 0 and j > 0: if s1[i-1] == s2[j-1]: LcsChars.append(s1[i-1]) LcsPositions.append((i-1, j-1)) # 存储原始索引(从0开始) i -= 1 j -= 1 elif dp[i-1][j] > dp[i][j-1]: i -= 1 else: j -= 1 # 回溯得到的是逆序,需要反转 LcsChars.reverse() LcsPositions.reverse() return LcsPositions, ''.join(LcsChars) def GetStringDiff(s1: str, s2: str) -> dict: """ 完整对比两个字符串,返回差异详情 :param s1: 原始字符串 :param s2: 目标字符串 :return: 差异字典(包含编辑距离、LCS、差异位置/内容) """ # 1. 计算编辑距离(差异程度) EditDist = LevenshteinDistance(s1, s2) # 2. 计算 LCS(公共部分) LcsPos, LcsStr = LongestCommonSubsequence(s1, s2) # 3. 定位差异位置和内容 DiffDetails = { "delete": [], # s1 中需要删除的字符 (索引, 字符) "insert": [], # s2 中需要插入的字符 (索引, 字符) "replace": [] # s1 中需要替换的字符 (s1索引, 原字符, 目标字符) } # 生成 s1 和 s2 的差异标记 S1Idx = 0 S2Idx = 0 for (LcsS1Idx, LcsS2Idx) in LcsPos: # 处理 s1 中需要删除的字符(LCS 前的非公共部分) while S1Idx < LcsS1Idx: DiffDetails["delete"].append((S1Idx, s1[S1Idx])) S1Idx += 1 # 处理 s2 中需要插入的字符(LCS 前的非公共部分) while S2Idx < LcsS2Idx: DiffDetails["insert"].append((S2Idx, s2[S2Idx])) S2Idx += 1 # 公共字符,跳过 S1Idx += 1 S2Idx += 1 # 处理末尾剩余的非公共部分 while S1Idx < len(s1): DiffDetails["delete"].append((S1Idx, s1[S1Idx])) S1Idx += 1 while S2Idx < len(s2): DiffDetails["insert"].append((S2Idx, s2[S2Idx])) S2Idx += 1 # 优化:将连续的删除+插入合并为替换(更符合直观) ReplaceCandidates = list(zip(DiffDetails["delete"], DiffDetails["insert"])) for (DelItem, InsItem) in ReplaceCandidates: DelIdx, DelChar = DelItem InsIdx, InsChar = InsItem DiffDetails["replace"].append((DelIdx, DelChar, InsChar)) DiffDetails["delete"].remove(DelItem) DiffDetails["insert"].remove(InsItem) return { "EditDistance": EditDist, # 差异程度(数值越小越相似) "similarity": 1 - EditDist / max(len(s1), len(s2), 1), # 相似度(0-1) "LcsString": LcsStr, # 最长公共子序列 "LcsLength": len(LcsStr), # LCS 长度 "DiffDetails": DiffDetails # 具体差异(删/插/改) } # 测试用例 if __name__ == "__main__": # 示例1:轻微差异(替换+插入) s1 = "Hello World!" s2 = "Hello Python!" DiffResult = GetStringDiff(s1, s2) print("=== 示例1:轻微差异 ===") print(f"编辑距离:{DiffResult['EditDistance']}") print(f"相似度:{DiffResult['similarity']:.2f}") print(f"最长公共子序列:{DiffResult['LcsString']}") print(f"差异详情:{DiffResult['DiffDetails']}") # 示例2:完全不同 s3 = "abc123" s4 = "xyz789" DiffResult2 = GetStringDiff(s3, s4) print("\n=== 示例2:完全不同 ===") print(f"编辑距离:{DiffResult2['EditDistance']}") print(f"相似度:{DiffResult2['similarity']:.2f}") print(f"最长公共子序列:{DiffResult2['LcsString']}") print(f"差异详情:{DiffResult2['DiffDetails']}") # 示例3:内容一致 s5 = "TestString" s6 = "TestString" DiffResult3 = GetStringDiff(s5, s6) print("\n=== 示例3:内容一致 ===") print(f"编辑距离:{DiffResult3['EditDistance']}") print(f"相似度:{DiffResult3['similarity']:.2f}") print(f"最长公共子序列:{DiffResult3['LcsString']}") print(f"差异详情:{DiffResult3['DiffDetails']}")