关于动态规划问题的题目我们已经介绍了很多,现在我们将介绍动态规划领域的另一个经典题目:最长公共子序列(Longest Common Subsequence, LCS)。此后我们还会介绍一些如本题这样有两个字符串的问题。
题目描述
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例:
输入:text1 = "abcdef", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
动态规划思路
-
定义一个二维数组
dp
:其中dp[i][j]
表示text1
的前i
个字符和text2
的前j
个字符的最长公共子序列的长度。之后我们介绍的很多2个串的题,大部分都是dp[i][j] 分别表示 a 串前i
个字符和 b 串前 j 个字符怎么怎么样。 -
初始化:
- 当
i = 0
或j = 0
时,dp[i][j] = 0
。因为没有字符时,最长公共子序列的长度为 0。
- 当
-
状态转移方程:
- 如果
text1[i-1] == text2[j-1]
(注意字符串索引从 0 开始,但这里比较的是当前位置之前的字符):
dp[i][j]=dp[i−1][j−1]+1
表示两个字符串的当前字符相同,最长公共子序列长度增加 1。 - 如果
text1[i-1] != text2[j-1]
:
dp[i][j]=max(dp[i−1][j],dp[i][j−1])
表示两个字符串的当前字符不同,最长公共子序列的长度等于不选择当前字符时的两个子问题的最大值。
- 如果
-
遍历顺序:
- 外层循环遍历
text1
的长度,内层循环遍历text2
的长度。
- 外层循环遍历
-
结果:
dp[len(text1)][len(text2)]
就是最长公共子序列的长度。
代码示例
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int n = text1.size();
int m = text2.size();
vector<vector<int>> dp(n, vector<int>(m, 0));
// dp[i][j]: t1[0~i],t2[0~j]两个子串的最长 公共子序列 的长度
for (int i = 0; i < n; i++) { // 处理第一列以及第一行
if (text1[i] == text2[0])
dp[i][0] = 1;
if (text1[i] != text2[0] && i > 0)
dp[i][0] = dp[i - 1][0];
}
for (int j = 0; j < m; j++) {
if (text1[0] == text2[j])
dp[0][j] = 1;
if (text1[0] != text2[j] && j > 0)
dp[0][j] = dp[0][j - 1];
}
for (int i = 1; i < n; i++) {
for (int j = 1; j < m; j++) {
if (text1[i] == text2[j]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[n - 1][m - 1];
}
};
此段代码示例与上面解题思路中的解法有一点差别。
代码步骤解析
- 初始化动态规划数组:
- 创建一个
n
行m
列的二维数组dp
,其中n
和m
分别是text1
和text2
的长度。所有元素初始化为 0。
- 创建一个
- 初始化第一行和第一列:
- 遍历
text1
的每个字符,如果它与text2
的第一个字符相同,则dp[i][0]
设置为 1(表示text1
的前i
个字符与text2
的第一个字符之间的最长公共子序列长度为 1)。如果不同,且i
大于 0,则dp[i][0]
继承dp[i-1][0]
的值(因为最长公共子序列可能不包含text1
的当前字符)。 - 类似地,遍历
text2
的每个字符,更新dp[0][j]
的值。
- 遍历
- 填充 dp 数组:
- 对于
dp
数组的其余部分(即i > 0
且j > 0
的情况),根据当前字符是否相等来决定dp[i][j]
的值:- 如果
text1[i] == text2[j]
,则dp[i][j]
等于左上角元素dp[i-1][j-1]
加 1(表示包含当前字符的最长公共子序列长度)。 - 如果
text1[i] != text2[j]
,则dp[i][j]
等于其上方元素dp[i-1][j]
和左方元素dp[i][j-1]
中的较大值(表示不包含当前字符的最长公共子序列长度)。
- 如果
- 对于
- 返回结果:
- 最后,
dp[n-1][m-1]
存储了text1
和text2
的最长公共子序列的长度,返回该值即可。
- 最后,