距离上次更新本文已经过去了 253 天,文章部分内容可能已经过时,请注意甄别
leetcode刷题笔记-1143.最长公共子序列
题目
https://leetcode.cn/problems/longest-common-subsequence/description/
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,”ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 示例 1: 输入:text1 = "abcde", text2 = "ace" 输出:3 解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2: 输入:text1 = "abc", text2 = "abc" 输出:3 解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3: 输入:text1 = "abc", text2 = "def" 输出:0 解释:两个字符串没有公共子序列,返回 0 。
|
提示
1 2
| 1 <= text1.length, text2.length <= 1000 text1 和 text2 仅由小写英文字符组成。
|
思路
这道题和718. 最长重复子数组的区别在于,718要求的是连续的子序列,而本题不要求子序列连续,就不是让你算子数组了。
虽然题目给出的是字符串,但本质上我们可以把它当作数组来处理,没有区别。
dp[i][j]
代表字符串a中i-1
和字符串b中j-1
下标之前的最长公共子序列的长度。
这样做就可以不对数组提前进行初始化了,因为dp[0][x]
和dp[x][0]
都是没有意义的。因为不会存在以下标-1
为结尾的字符串,也自然没有公共子序列。换句话说,长度为0的字符串是不会有公共子序列的。
依照dp数组,可以想出递推的公式,当a[i-1]
和b[j-1]
相同的时候,就说明子序列可以被扩展,dp[i][j] == dp[i-1][j-1]+1
。
如果不相同,则代表子序列断了,但注意我们dp数组的含义,它代表的是i-1和j-1之前的最长公共子序列的长度,即便i-1和j-1不相等,这个最长公共子序列依旧是有一个取值的,即选用“前一位”的公共子序列长度最大值。
但由于dp是一个二维数组,这里的“前一位”就有两个情况了,分别是dp[i-1][j]
和dp[i][j-1]
,对应含义是字符串a中前一位(根据dp数组,下标是i-1,公共子序列的含义是i-2)和b[j-1]
能构成的最长公共子序列,以及字符串b中前一位和a[i-1]
能构成的最长公共子序列的长度。需要用max来选取二者的最大值。
递推的代码如下。
1 2 3 4 5
| if (a[i - 1] == b[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); }
|
根据递推公式,dp[i][j]
的值可以从dp数组中的三个方向推导出来,如下图所示。

剩下的代码就很简单了,因为我们不需要对dp数组进行提前初始化,直接用vector构造函数统一初始化为0就可以了,然后从1开始遍历直到字符串末尾,维护一个最大值,返回即可。
其实不维护最大值也是可以的,因为根据dp数组的定义,dp[a.size()][b.size()]
就是我们需要的最大值。
代码1
完整的代码如下,具体参考注释中的说明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Solution { public: int longestCommonSubsequence(string a, string b) { int result = 0; vector<vector<int>> dp(a.size() + 1, vector<int>(b.size() + 1, 0)); for (int i = 1; i <= a.size(); i++) { for (int j = 1; j <= b.size(); j++) { if (a[i - 1] == b[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); } if (dp[i][j] > result) { result = dp[i][j]; } } } return result; } };
|

代码2:提前初始化
依照718题的思路,我们也可以写出一个提前初始化dp数组的代码,完整代码如下。
注意初始化的情况也有所不同,因为本题要求的是子序列,可以出现不连续的情况,即便a[i] != b[0]
,我们也需要沿用之前dp[i - 1][0]
的结果,来保证初始化是正确的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class Solution { public: int longestCommonSubsequence(string a, string b) { int result = 0; vector<vector<int>> dp(a.size(), vector<int>(b.size(), 0)); for (int i = 0; i < a.size(); i++) { if (a[i] == b[0]) { dp[i][0] = 1; result = 1; } else if (i > 0) { dp[i][0] = dp[i - 1][0]; } } for (int j = 0; j < b.size(); j++) { if (b[j] == a[0]) { dp[0][j] = 1; result = 1; } else if (j > 0) { dp[0][j] = dp[0][j - 1]; } } for (int i = 1; i < a.size(); i++) { for (int j = 1; j < b.size(); j++) { if (a[i] == b[j]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); } if (dp[i][j] > result) { result = dp[i][j]; } } } return result; } };
|
