
1) 【一句话结论】解决最长公共子序列(LCS)问题,可通过动态规划定义状态为前i个字符与前j个字符的LCS长度,状态转移方程为字符相等时加1,否则取前缀最大值,边界为初始全0,空间优化通过滚动数组将O(mn)空间降为O(n)。
2) 【原理/概念讲解】
老师:我们以最长公共子序列为例,核心是定义状态并推导转移。设字符串a长度为m,b长度为n,定义dp[i][j]为a的前i个字符(a[0..i-1])和b的前j个字符(b[0..j-1])的最长公共子序列(LCS)长度。
类比:就像拼图,每一步看当前字符是否匹配,匹配则“拼上”,否则从左边或上边找最长。
状态转移方程:
dp[i][j] = dp[i-1][j-1] + 1;dp[i][j] = max(dp[i-1][j], dp[i][j-1])。dp[0][*]=0、dp[*][0]=0(空字符串与任何字符串的LCS长度为0)。dp[i][j]仅依赖上一行(dp[i-1][*]),可用一维数组滚动,将空间复杂度从O(mn)降至O(n),时间复杂度仍为O(mn)。3) 【对比与适用场景】
| 比较项 | 最长公共子序列(LCS) | 最长公共子串(Longest Common Substring) |
|---|---|---|
| 定义 | 不要求字符连续 | 要求字符连续(原字符串中位置相邻) |
| 状态转移 | dp[i][j] = a[i-1]==b[j-1] ? dp[i-1][j-1]+1 : max(dp[i-1][j], dp[i][j-1]) | dp[i][j] = a[i-1]==b[j-1] ? dp[i-1][j-1]+1 : 0(不连续则重置) |
| 空间优化 | 一维滚动数组(O(n)) | 通常需二维(连续性依赖对角线) |
| 适用场景 | 文本匹配、序列比对(如DNA序列) | 检测连续匹配(如字符串搜索) |
4) 【示例】
字符串a="ABCBDAB",b="BDCABA",计算LCS。
伪代码:
m, n = len(a), len(b)
dp = [0] * (n+1)
for i in range(1, m+1):
prev = 0 # 上一行第一个元素(初始为0)
for j in range(1, n+1):
temp = dp[j] # 保存当前dp[j],用于下一轮更新
if a[i-1] == b[j-1]:
dp[j] = prev + 1
else:
dp[j] = max(dp[j], dp[j-1])
prev = temp # 更新prev为上一行当前j的值
print(dp[n]) # 结果为4(LCS为"BCBA"或"BDAB"等)
5) 【面试口播版答案】
您好,针对最长公共子序列问题,核心思路是用动态规划定义状态为前i个字符和前j个字符的LCS长度。设字符串a长度为m,b长度为n,dp[i][j]表示a前i个与b前j个的LCS长度。状态转移方程:若a[i-1]等于b[j-1],则dp[i][j] = dp[i-1][j-1] + 1;否则取dp[i-1][j]和dp[i][j-1]的最大值。边界条件是i或j为0时,dp值为0(空字符串与任何字符串的LCS为0)。空间优化通过一维数组滚动,将空间复杂度从O(mn)降至O(n),时间复杂度保持O(mn)。这样就能高效计算最长公共子序列的长度。
6) 【追问清单】
dp[i][j]仅依赖上一行(dp[i-1][*])和当前列前一个元素(dp[i][j-1]),只需保留上一行和当前行数据,实现滚动。dp值为0,这是递归的基例(空字符串与任何字符串的LCS为0)。7) 【常见坑/雷区】
dp[i][j]定义为子串长度,导致转移方程错误。dp值计算错误。