给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
注:两个字符串的「公共子序列」(Longest Common Subsequence,简称 LCS)是这两个字符串所共同拥有的子序列。若这两个字符串没有公共子序列,则返回 0。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace",它的长度为 3。
示例 2:
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc",它的长度为 3。
示例 3:
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0。
遇到子序列相关的问题,可以往动态规划上考虑。这道题让我们在两个字符串中寻找共同拥有的最长子序列,因此很容易想到用二维动态规划来解决。
定义$dp[i][j]$为:$str1[0...i-1]和str2[0...j-1]$的最长公共子序列长度。
根据上述dp数组的定义,可以写出如下Base Case:
- i为0时,str1[0...i-1]不构成一个字符串,因此不存在与str2的公共子序列,因此dp[0][j]为0
- j为0时,str2[0...j-1]不构成一个字符串,因此不存在于str1的公共子序列,因此dp[i][0]为0
当str1遍历到i - 1,str2遍历到j - 1时,即要求$str1[0...i-1]和str2[0...j-1]$的最长公共子序列长度时,需要考虑以下的几种情况:
- 如果str1[i - 1]与str2[j - 1]相等,那么肯定要将其放入str1[0...i−1]和str2[0...j−1]的LCS当中,有了这个字符,LCS的长度就会加1,因此
$$dp[i][j] = dp[i - 1][j - 1] + 1$$ - 如果str1[i - 1]与str2[j - 1]不等,则又会分为以下的三种情况:
-
str1[i - 1]与str2[j - 1]都不放入LCS当中,那么LCS的长度不会产生变化,即dp[i][j] = dp[i - 1][j - 1]
-
str1[i - 1]放入LCS中,但str2[j - 1]不放。这时dp[i][j] = dp[i][j - 1]
-
str1[i - 1]不放入LCS中,但str2[j - 1]放。这时dp[i][j] = dp[i - 1][j]
由于在dp[i][j]这个位置可以做上述三种选择,因此取三种选择可能产生的最大值,即为dp[i][j]。即:
$$dp[i][j] = max(dp[i - 1][j - 1],dp[i][j - 1],dp[i - 1][j])$$
-
public int longestCommonSubsequence(String str1, String str2) {
int[][] dp = new int[str1.length() + 1][str2.length() + 1];
//Base Case,其实可以不用,因为数组初始化时全体元素就为0
for(int j = 0; j <= str2.length(); j++)
dp[0][j] = 0;
for(int i = 0; i <= str1.length(); i++)
dp[i][0] = 0;
for(int i = 1; i <= str1.length(); i++){
for(int j = 1; j <= str2.length(); j++){
if(str1.charAt(i - 1) == str2.charAt(j - 1))
dp[i][j] = dp[i - 1][j - 1] + 1;
else
dp[i][j] = Math.max(dp[i - 1][j - 1], Math.max(dp[i][j - 1], dp[i - 1][j]));
}
}
return dp[str1.length()][str2.length()];
}
- 时间复杂度:O(m * n)
- 空间复杂度:O(m * n) 其中,m和n分别为str1和str2的长度