指尖划过的轨迹,藏着最细腻的答案~
题目:
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"是"abcde"的子序列,但"aec"不是"abcde"的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。
提示:
1 <= text1.length, text2.length <= 1000text1和text2仅由小写英文字符组成。
分析:
动态规划一般分为3步走:
-
确定dp数组含义: 我们定义
dp[i][j]为text1从下标(0, i)与text2从下标(0,j)的最长的公共子序列。最终答案即为dp[m-1][n-1] -
状态转移方程:
对于
dp[i][j]来说:- 如果
text1[i] == text2[j],两个字符串的字符相同,则最长公共子序列必定加1; - 如果
text1[i] != text2[j],我们思考怎样可以到达dp[i][j]呢?从i-1到达i,即text1向后遍历一位,或者从j-1到达j,即tex2向后遍历一位,因此此时的最大的公共子序列为dp[i-1][j]与dp[i][j-1]中的最大值。
最终的状态转移方程如下:
- 如果
-
初始化: 如果
i=0与j=0时,上面状态转移方程不合法。因此我们可以在i=0的上面与j=0左边增加新的一行或一列数据,此时上面状态转移方程i需要变为i+1,i-1变为i,如下:
AC代码:
class Solution {
public:
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 = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (text1[i] == text2[j]) {
dp[i+1][j+1] = dp[i][j] + 1;
} else {
dp[i+1][j+1] = max(dp[i+1][j], dp[i][j+1]);
}
}
}
return dp[m][n];
}
};
空间优化一(两个数组):
我们可以观察到上面的状态转移方程只与i-1与j-1有关,因此我们可以使用两个数组来轮转记录前一次的状态。
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.size(), n = text2.size();
vector<vector<int>> dp(2, vector<int>(n + 1, 0));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (text1[i] == text2[j]) {
dp[(i+1) % 2][j+1] = dp[i % 2][j] + 1;
} else {
dp[(i+1) % 2][j+1] = max(dp[(i+1) % 2][j], dp[i % 2][j+1]);
}
}
}
return dp[m % 2][n];
}
};