最长公共子序列 LCS
问题描述
给定两个字符串,找出它们的最长公共子序列的长度。子序列不需要连续。
示例:
1 2 3
| 输入:text1 = "abcde", text2 = "ace" 输出:3 解释:最长公共子序列是 "ace",长度为3
|
动态规划解法
解题思路
- 创建二维dp数组,
dp[i][j]表示text1[0..i-1]和text2[0..j-1]的LCS长度
- 初始化:空字符串的LCS为0
- 状态转移:
- 当
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])
- 最终结果:
dp[m][n](m和n分别是两个字符串的长度)
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function longestCommonSubsequence(text1, text2) { const m = text1.length, n = text2.length; const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0)); for (let i = 1; i <= m; i++) { for (let 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] = Math.max(dp[i-1][j], dp[i][j-1]); } } } return dp[m][n]; }
|
复杂度分析
空间优化解法
可以将空间复杂度优化到O(min(m,n)):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function longestCommonSubsequence(text1, text2) { if (text1.length < text2.length) { [text1, text2] = [text2, text1]; } const m = text1.length, n = text2.length; let prev = new Array(n+1).fill(0); let curr = new Array(n+1).fill(0); for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { if (text1[i-1] === text2[j-1]) { curr[j] = prev[j-1] + 1; } else { curr[j] = Math.max(prev[j], curr[j-1]); } } [prev, curr] = [curr, prev]; } return prev[n]; }
|
重建LCS序列
如果需要返回具体的LCS序列而不仅仅是长度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| function getLCS(text1, text2) { const m = text1.length, n = text2.length; const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0)); for (let i = 1; i <= m; i++) { for (let 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] = Math.max(dp[i-1][j], dp[i][j-1]); } } } let i = m, j = n; const lcs = []; while (i > 0 && j > 0) { if (text1[i-1] === text2[j-1]) { lcs.unshift(text1[i-1]); i--; j--; } else if (dp[i-1][j] > dp[i][j-1]) { i--; } else { j--; } } return lcs.join(''); }
|
示例测试
1 2 3
| console.log(longestCommonSubsequence("abcde", "ace")); console.log(longestCommonSubsequence("abc", "def")); console.log(getLCS("abcde", "ace"));
|
算法图解
以text1=”abcde”和text2=”ace”为例:
初始化dp表((m+1)×(n+1)的零矩阵)
填充dp表:
- 当字符匹配时,取左上角值+1
- 否则取上方或左方的最大值
最终dp表右下角的值即为LCS长度
a c e
a 1 1 1
b 1 1 1
c 1 2 2
d 1 2 2
e 1 2 3
总结
最长公共子序列问题通过动态规划可以高效解决:
- 定义dp数组表示子问题解
- 找出状态转移方程
- 填充dp表
- 从dp表获取结果
空间优化版本适合处理大字符串,而重建序列版本可以获取具体的LCS字符串。