摘要
最长公共子序列(LCS)是一种在两个字符串中找出顺序一致但不要求连续的最长共同子串的算法。通过生活中的比喻,如“找共同回忆”和“比对错题本”,可以形象地理解LCS的原理。LCS的核心规则是顺序一致但允许跳过不同内容,不要求连续。动态规划是解决LCS问题的常用方法,通过“搭桥过河”和“表格填空”的方式,逐步计算最长公共子序列的长度。LCS在文本比对、DNA序列分析、拼写纠错等领域有广泛应用。通过具体的例子和代码实现,可以更直观地掌握LCS的计算过程。
一、什么是最长公共子序列(LCS)?
最长公共子序列,英文叫 Longest Common Subsequence,简称 LCS。
它的意思是:
在两个字符串中,找出最长的、顺序一致但不要求连续的“共同子串”。
二、生活中的形象比喻
1. “找共同回忆”的故事
想象你和你的好朋友小明,从小到大经历了很多事情。你们各自有一份“成长日记”,里面记录了自己经历的事件(比如:上幼儿园、学游泳、考钢琴、旅游、毕业……)。
现在,你们想找出你们共同经历过的、顺序一致的最长回忆片段,但允许中间有些回忆你有他没有,或者他有你没有。
这段最长的共同回忆,就是你们的“最长公共子序列”。
举个例子
- 你的日记:A B C D E F G
- 小明的日记:B D F H
你们的最长共同回忆是什么?
答案是:B D F
因为B、D、F都在你们各自的日记里,并且顺序一致(虽然中间有些回忆不同,但没关系)。
2. “比对错题本”的故事
你和同桌都做了一套题,记录了自己做对的题号。现在老师让你们找出做对题号顺序一致的最长列表,这就是LCS。
三、LCS的“游戏规则”
- 顺序要一致,但可以跳过不一样的内容。
- 不要求连续,只要顺序对上就行。
四、LCS的“找法”——动态规划的形象理解
1. “搭桥过河”游戏
想象有两条河岸,左岸是你的一串字符,右岸是小明的一串字符。你要从左岸走到右岸,每次只能往右走一步,遇到相同的字符就搭一座桥,走到对岸。
你要找出最多能搭几座桥,这就是LCS的长度。
2. “表格填空”法
我们可以画一个表格,横轴是你的字符串,纵轴是小明的字符串。每个格子里记录“到这里为止,最长的共同子序列有多长”。
- 如果当前字符一样,就在左上角的格子基础上+1。
- 如果不一样,就取左边或上边的最大值。
最后,右下角的数字,就是LCS的长度。
五、LCS的代码小片段(伪代码)
for i in range(1, len(A)+1):
for j in range(1, len(B)+1):
if A[i-1] == B[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的实际应用
- 文本比对:比如Git的diff、查找文档相似度。
- DNA序列分析:找出不同生物基因的相似片段。
- 拼写纠错、自动补全等。
七、总结口诀
- 顺序一致,允许跳跃,最长相同,子序列找。搭桥过河,表格填空,LCS帮你找共同。
我们继续用生动形象的方式,详细讲解最长公共子序列(LCS)的动态规划推导过程,并举一个完整的例子,带你一步步“搭桥过河”,让你彻底明白LCS是怎么一步步算出来的!
一、举个具体例子
假设有两个字符串:
- 字符串A:
A B C D E
- 字符串B:
B D C A E
我们要找它们的最长公共子序列。
二、画表格——“搭桥过河”现场
我们画一个表格,横着写A的字符,竖着写B的字符。
表格的每个格子dp[i][j]
,表示A的前i个字符和B的前j个字符的LCS长度。
"" A B C D E
+----------------------
"" | 0 0 0 0 0 0
B | 0
D | 0
C | 0
A | 0
E | 0
三、一步步填表格
我们从左上角开始,逐行逐列填:
1. 第一行和第一列都填0(因为有一个字符串是空,LCS为0)
2. 逐步填格子
- 如果A[i-1] == B[j-1],说明可以“搭桥”,
dp[i][j] = dp[i-1][j-1] + 1
- 否则,
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
,即“跳过”一个字符,取最大值
具体填表过程(只写关键步骤):
- 比如A的第1个字符A,B的第1个字符B,不一样,
dp[1][1]=0
- A的第2个字符B,B的第1个字符B,一样,
dp[2][1]=dp[1][0]+1=1
- 继续填下去……
最终表格如下(你可以自己在纸上画一画,体会“搭桥”的过程):
"" A B C D E
+----------------------
"" | 0 0 0 0 0 0
B | 0 0 1 1 1 1
D | 0 0 1 1 2 2
C | 0 0 1 2 2 2
A | 0 1 1 2 2 2
E | 0 1 1 2 2 3
四、怎么找出LCS的内容?
从右下角(3)开始“倒着走”:
- 如果当前格子是由左上角+1得到的,说明A[i-1]==B[j-1],把这个字符记下来。
- 否则,往左或往上走,看哪个值大。
本例中,LCS长度是3,内容是:B C E 或 B D E 或 A C E(有多种可能)
五、形象总结
- 每个格子就是你和小明“走到这里为止,最长共同回忆有多长”
- 搭桥就是遇到相同的回忆,桥越多,LCS越长
- 填表格就像走迷宫,最后右下角就是答案
六、代码实现(Python版)
def lcs(A, B):
m, n = len(A), len(B)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if A[i-1] == B[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
七、LCS的“生活应用”再举例
- 查论文抄袭:比对两篇文章的最长公共子序列,越长越可能有抄袭。
- DNA比对:找出两条基因链的最长相同片段。
- 文件版本对比:比如Git diff,找出两个文件的最大相同部分。
八、口诀升级版
- 搭桥过河,表格填空,遇到相同,桥加一根。顺序一致,允许跳跃,最长回忆,LCS找。