数据结构与算法-最长公共子序列
大家好,欢迎回到我们的算法学习系列。今天,我们将探讨一个经典的动态规划问题——最长公共子序列(Longest Common Subsequence,简称 LCS)。这个问题在字符串处理和比较中非常常见。
什么是最长公共子序列?
最长公共子序列是指在两个序列中,按照顺序出现但不一定连续出现的最长子序列。例如,字符串 ABCBDAB
和 BDCABA
的最长公共子序列是 BCBA
或 BDAB
,长度为 4。
问题描述
给定两个字符串 text1
和 text2
,返回这两个字符串的最长公共子序列的长度。
示例
- 输入:
text1 = "abcde"
,text2 = "ace"
输出:3
(最长公共子序列是 “ace”) - 输入:
text1 = "abc"
,text2 = "abc"
输出:3
(最长公共子序列是 “abc”)
解决思路
我们可以使用动态规划(Dynamic Programming)来解决这个问题。动态规划通过将问题分解为子问题,并将子问题的结果存储起来,以便后续计算使用,从而提高效率。
动态规划的步骤
- 定义状态:定义一个二维数组
dp
,其中dp[i][j]
表示字符串text1
的前i
个字符与字符串text2
的前j
个字符的最长公共子序列长度。 - 初始化:初始化
dp
数组的第一行和第一列,因为如果一个字符串是空串,则最长公共子序列的长度为 0。 - 状态转移:对于每个字符
text1[i-1]
和text2[j-1]
:- 如果
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]
即为text1
和text2
的最长公共子序列长度,其中m
和n
分别是text1
和text2
的长度。
实现代码
下面是用 JavaScript 实现的最长公共子序列算法:
function longestCommonSubsequence(text1, text2) {
const m = text1.length;
const n = text2.length;
const dp = Array.from({ length: m + 1 }, () => 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];
}
代码解析
- 初始化二维数组:创建一个大小为
(m+1) x (n+1)
的二维数组dp
,并初始化为 0。 - 双重循环遍历:使用双重循环遍历字符串
text1
和text2
的每个字符。 - 状态转移:
- 如果字符相同,则
dp[i][j] = dp[i-1][j-1] + 1
。 - 如果字符不同,则
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
。
- 如果字符相同,则
- 返回结果:
dp[m][n]
即为两个字符串的最长公共子序列长度。
图解
让我们通过图解来理解动态规划的过程:
假设 text1 = "abcde"
和 text2 = "ace"
:
初始化 dp
数组:
'' a b c d e
'' 0 0 0 0 0 0
a 0 0 0 0 0 0
c 0 0 0 0 0 0
e 0 0 0 0 0 0
填充 dp
数组:
'' a b c d e
'' 0 0 0 0 0 0
a 0 1 1 1 1 1
c 0 1 1 2 2 2
e 0 1 1 2 2 3
最终结果 dp[5][3] = 3
,即最长公共子序列的长度为 3。
小结
今天,我们学习了最长公共子序列算法的原理、实现方法和应用场景。通过动态规划的方法,我们可以高效地求解两个字符串的最长公共子序列长度,这在字符串处理和比较中非常常用。
感谢大家的阅读!如果你有任何问题或建议,欢迎在评论区留言。让我们一起在算法的世界中不断学习和进步!