最长公共子序列(Longest Common Subsequence,简称LCS)是计算机科学中经典的字符串处理问题,在文本比对、版本控制、生物信息学等领域有广泛应用。LeetCode 1143题正是这一问题的标准实现,要求找出两个字符串的最长公共子序列的长度。
与子串不同,子序列不要求字符连续出现,只要保持相对顺序即可。例如"abcde"和"ace"的LCS是"ace",长度为3。这个问题看似简单,但能很好地考察动态规划思想的掌握程度。
我们定义dp[i][j]表示text1前i个字符和text2前j个字符的LCS长度。状态转移方程分两种情况:
当text1[i-1] == text2[j-1]时:
dp[i][j] = dp[i-1][j-1] + 1
当字符不等时:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
这个方程的核心思想是:如果当前字符匹配,LCS长度增加1;否则继承左侧或上方的最大值。
dp数组需要多开一行一列作为边界条件,即dp[0][j]和dp[i][0]都初始化为0,表示空字符串与任何字符串的LCS长度为0。
python复制def longestCommonSubsequence(text1: str, text2: str) -> int:
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i-1] == text2[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]
由于dp[i][j]只依赖于当前行和上一行,可以将空间复杂度从O(mn)优化到O(min(m,n)):
python复制def longestCommonSubsequence(text1: str, text2: str) -> int:
if len(text1) < len(text2):
text1, text2 = text2, text1
m, n = len(text1), len(text2)
prev = [0] * (n + 1)
curr = [0] * (n + 1)
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i-1] == text2[j-1]:
curr[j] = prev[j-1] + 1
else:
curr[j] = max(prev[j], curr[j-1])
prev, curr = curr, prev
return prev[n]
基础实现的时间复杂度为O(mn),空间复杂度O(mn)。优化后的空间复杂度为O(min(m,n))。
Git等版本控制系统使用LCS算法来比较文件差异,生成可读的diff输出。
DNA序列比对中,寻找基因序列的相似区域本质上就是LCS问题。
通过计算输入与词典单词的LCS距离,可以实现更智能的拼写建议。
常见错误包括:
可以打印dp表格来验证状态转移是否正确:
python复制def print_dp(dp, text1, text2):
print(" " + " ".join(" " + c for c in text2))
for i in range(len(dp)):
row = (text1[i-1] if i > 0 else " ") + " "
row += " ".join(f"{num:2d}" for num in dp[i])
print(row)
对于特定字符集(如DNA的ACGT),可以利用位并行技术进一步优化。
Hirschberg算法可以在O(mn)时间和O(min(m,n))空间内同时计算出LCS长度和具体序列。
由于dp表格的填充有特定的依赖关系,可以采用对角线并行的方法加速计算。
cpp复制int longestCommonSubsequence(string text1, string text2) {
int m = text1.size(), n = text2.size();
vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
for(int i=1; i<=m; ++i) {
for(int j=1; j<=n; ++j) {
if(text1[i-1] == text2[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];
}
Java中二维数组的内存分配方式会影响性能,可以考虑使用一维数组优化。
全面的测试应该包括:
示例测试用例:
python复制assert longestCommonSubsequence("abcde", "ace") == 3
assert longestCommonSubsequence("abc", "def") == 0
assert longestCommonSubsequence("", "abc") == 0
assert longestCommonSubsequence("abc", "abc") == 3
面试官可能会从以下角度深入提问:
在实际编码中,我发现以下几点特别重要:
text1[i-1]而不是text1[i]来避免索引错误这个问题的美妙之处在于,它用简单的形式展示了动态规划的核心思想,是理解更复杂DP问题的重要基础。我建议每个学习算法的同学都要彻底掌握这个经典问题。