05 August 2008
Given two strings text1 and text2, return the length of their longest common subsequence.
A subsequence of a string is a new string generated from the original string with some characters(can be none) deleted without changing the relative order of the remaining characters. (eg, “ace” is a subsequence of “abcde” while “aec” is not). A common subsequence of two strings is a subsequence that is common to both strings.
If there is no common subsequence, return 0.
Example 1:
1
2
3
Input: text1 = "abcde", text2 = "ace"
Output: 3
Explanation: The longest common subsequence is "ace" and its length is 3.
Example 2:
1
2
3
Input: text1 = "abc", text2 = "abc"
Output: 3
Explanation: The longest common subsequence is "abc" and its length is 3.
Example 3:
1
2
3
Input: text1 = "abc", text2 = "def"
Output: 0
Explanation: There is no such common subsequence, so the result is 0.
Constraints:
这个是典型的二维DP,可以看作以下的DP表格:
1) 状态定义,为了方便理解此表,暂时认为索引是从 1 开始的,待会的代码中只要稍作调整即可。其中,dp[i][j] 的含义是:对于 s1[1..i] 和 s2[1..j],它们的 LCS 长度是 dp[i][j]。
比如上图的例子,d[2][4] 的含义就是:对于 “ac” 和 “babc”,它们的 LCS 长度是 2。最终想得到的答案应该是 dp[3][6]。
2) 初始化base case,专门让索引为 0 的行和列表示空串,dp[0][..] 和 dp[..][0] 都应该初始化为 0,这就是 base case。比如说,按照刚才 dp 数组的定义,dp[0][3]=0 的含义是:对于字符串 “” 和 “bab”,其 LCS 的长度为 0。因为有一个字符串是空串,它们的最长公共子序列的长度显然应该是 0。
注:二维DP表中,初始化的两条边在数组创建后本身就为0。
3)这是动态规划最难的一步,不过这种字符串问题的套路都差不多。状态转移说简单些就是做选择,比如说这个问题,是求 s1 和 s2 的最长公共子序列,不妨称这个子序列为 lcs。那么对于 s1 和 s2 中的每个字符,有什么选择?很简单,两种选择,要么在 lcs 中,要么不在。
这个「在」和「不在」就是选择,关键是,应该如何选择呢?这个需要动点脑筋:如果某个字符应该在 lcs 中,那么这个字符肯定同时存在于 s1 和 s2 中,因为 lcs 是最长公共子序列嘛。所以本题的思路是这样:
对于第一种情况,找到一个
中的字符,同时将 1
lcs
1
i
向前移动一位,并给 1
j
的长度加一;对于后者,则尝试两种情况,取更大的结果。相应代码(以从后向前为例,这道题从前往后也可以的):1
lcs
1
2
3
4
5
6
7
8
9
10
for i in range(1, m + 1):
for j in range(1, n + 1):
if str1[i - 1] == str2[j - 1]:
# 找到一个 lcs 中的字符
dp[i][j] = 1 + dp[i-1][j-1]
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[-1][-1]
对于
和 1
s1[i]
不相等的情况,至少有一个字符不在 1
s2[j]
中,会不会两个字符都不在呢?比如下面这种情况:1
lcs
相应转移方程的代码为:
1
2
3
4
5
6
7
if str1[i - 1] == str2[j - 1]:
# ...
else: # 考虑有三种情况
dp[i][j] = max(dp[i-1][j],
dp[i][j-1],
dp[i-1][j-1])
其实这样改也能得到正确答案,只是多此一举,因为
永远是三者中最小的,max 根本不可能取到它。1
dp[i-1][j-1]
4)考虑状态压缩,两个字符串各占一维,无法状态压缩。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int len1 = text1.length();
int len2 = text2.length();
int[][] dp = new int[len1 + 1][len2 + 1]; // 多一位是包含0个字符和0个字符的情况,初始两条为0
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (text1.charAt(i - 1) == text2.charAt(j - 1)) { // index为0的第一个字符,为dp数组中的第2个(从1开始),dp数组中的第一个元素对应空字符串
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// 谁能让lcs更大,就听谁的,dp[i - 1][j - 1]不用考虑
dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
}
}
}
return dp[len1][len2];
}
}