-
LeetCode链接:1143. 最长公共子序列
-
算法策略 : 动态规划(Dynamic Programming)
-
最长公共子序列(Longest Common Subsequence,LCS)
求两个序列的最长公共子序列长度
[1,3,5,9,10] 和 [1,4,9,10] 的最长公共子序列是 [1,9,10],长度为3
ABCBDAB 和 BDCABA 的最长公共子序列长度为4,可能是BAAB、BDAB、BCAB、BCBA
思路
-
假设2个序列分别是nums1、nums2
i ∈ [1,nums1.length]
j ∈ [1,nums2.length] -
假设dp(i, j)是【nums1前i个元素】与【nums2前j个元素】的最长公共子序列长度
-
dp(i, 0)、dp(0, j)初始值均为0
-
如果nums1[i - 1] = nums2[j - 1],那么dp(i, j) = dp(i - 1, j - 1) + 1
-
如果nums1[i - 1] != nums2[j - 1],那么dp(i, j) = max { dp(i - 1, j), dp(i, j - 1) }
解法1 - 递归实现
int lcs(int [] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0) return 0;
if (nums2 == null || nums2.length == 0) return 0;
return lcs(nums1, nums2.length, nums2, num2.length);
}
int lcs(int[] nums1, int i, int[] nums2, int j) {
if (i == 0 || j == 0) return 0;
if (nums1[i - 1] != nums2[j - 1]) {
return Math.max(
lcs(nums1, i - 1, nums2, j),
lcs(nums2, i, nums2, j - 1));
}
return lcs(nums1, i - 1, nums2, j - 1) + 1;
}
- 空间复杂度:O(k),k = min { n, m },n、m是2个序列的长度
- 空间复杂度:O(2^n),当 n = m 时
递归实现分析
- 出现了重复递归调用
解法2 - 非递归实现
int lcs(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0) return 0;
if (nums2 == null || nums2.length == 0) return 0;
int[][] dp = new int[num1.length + 1][nums2.length + 1];
for (int i = 1; i <= nums1.length; i++) {
for (int j = 1; j <= nums2.length; j++) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[nums1.length][nums2.length];
}
- 空间复杂度:O(n * m)
- 时间复杂度:O(n * m)
- dp数组的计算结果如下所示
非递归实现 - 优化- 滚动数组
- 可以使用滚动数组优化空间复杂度
int lcs(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0) return 0;
if (nums2 == null || nums2.length == 0) return 0;
int[][] dp = new int[2][nums2.length + 1];
for (int i = 1; i <= nums1.length; i++) {
int row = i & 1;
int prevRow = (i - 1) & 1;
for (int j = 1; j <= nums2.length; j++) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[row][j] = dp[prevRow][j - 1] + 1;
} else {
dp[row][j] = Math.max(dp[prevRow][j], dp[row][j - 1]);
}
}
}
return dp[nums1.length & 1][nums2.length];
}
非递归实现 - 优化 - 一维数组
- 可以将二维数组优化成一维数组,进一步降低空间复杂度
int lcs(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0) return 0;
if (nums2 == null || nums2.length == 0) return 0;
int[] dp = new int[nums2.length + 1];
for (int i = 1; i <= nums1.length; i++) {
int cur = 0;
for (int j = 1; j <= nums2.length; j++) {
int leftTop = cur;
cur = dp[j];
if (nums1[i - 1] == nums2[j - 1]) {
dp[j] = leftTop + 1;
} else {
dp[j] = Math.max(dp[j], dp[j - 1]);
}
}
}
return dp[nums2.length];
}
- 可以将空间复杂度优化至O(k),k = min { n, m }
int lcs(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0) return 0;
if (nums2 == null || nums2.length == 0) return 0;
int[] rowsNums = nums1, colsNums = nums2;
if (nums1.length < nums2.length) {
colsNums = nums1;
rowsNums = nums2;
}
int[] dp = new int[colsNums.length + 1];
for (int i = 1; i <= rowsNums.length; i++) {
int cur = 0;
for (int j = 1; j <= colsNums.length; j++) {
int leftTop = cur;
cur = dp[j];
if (rowsNums[i - 1] == colsNums[j - 1]) {
dp[j] = leftTop + 1;
} else {
dp[j] = Math.max(dp[j], dp[j - 1]);
}
}
}
return dp[colsNums.length];
}
leetcode 版本
public int longestCommonSubsequence(String text1, String text2) {
if (text1 == null || text2 == null) return 0;
char[] chars1 = text1.toCharArray();
if (chars1.length == 0) return 0;
char[] chars2 = text2.toCharArray();
if (chars2.length == 0) return 0;
char[] rowsChars = chars1, colsChars = chars2;
if (chars1.length < chars2.length) {
colsChars = chars1;
rowsChars = chars2;
}
int[] dp = new int[colsChars.length + 1];
for (int i = 1; i <= rowsChars.length; i++) {
int cur = 0;
for (int j = 1; j <= colsChars.length; j++) {
int leftTop = cur;
cur = dp[j];
if (rowsChars[i - 1] == colsChars[j - 1]) {
dp[j] = leftTop + 1;
} else {
dp[j] = Math.max(dp[j], dp[j - 1]);
}
}
}
return dp[colsChars.length];
}