问题描述
- 给出两个字符串,找到最长公共子序列(LCS),返回LCS的长度。
- Example :
给出”ABCD” 和 “EDCA”,这个LCS是 “A” (或 D或C),返回1
给出 “ABCD” 和 “EACB”,这个LCS是”AC”返回 2 - 牛客地址
- LintCode地址
问题分析
- 最长公共子序列问题是动态规划经典题目,我将从暴力递归,到记忆化搜索,到动态规划,一步步讲解。
暴力递归:
设置递归函数,用int findLCS(char[] a, int i, char[] b, int j)
返回a[i ~ n]
与b[j ~ m]
的最长公共子序列的长度。- 如果
a[i] == a[j]
,那么 便应该返回1 + findLCS(a, i + 1, b, j + 1)
- 如果
a[i] != a[j]
,那么 便应该返回Math.max(findLCS(a, i + 1, b, j), findLCS(a, i, b, j + 1))
这是一种将
i
与j
看作起点的逻辑,当然也可以将i
与j
看作终点, 即用int findLCS(char[] a, int i, char[] b, int j)
返回a[0 ~ i]
与b[0 ~ j]
的最长公共子序列的长度。- 如果
记忆化搜索
因为暴力递归存在大量重复计算,用memory[i][j]
记忆a[i ~ n]
与b[j ~ m]
的最长公共子序列的长度, 初始化都为 -1,如果当前值不为-1,说明已经计算过,无需继续递归,直接返回。否则,则计算,然后将计算后的结果存入memory[i][j]
中。
时间复杂度:O(N^2)动态规划
和暴力递归同样的状态,用dp[i][j]
存储a[i ~ n]
与b[j ~ m]
的最长公共子序列的长度。根据递归关系,设置状态转移条件。求解。
当然,也可以用dp[i][j]
来表示a[0 ~ i]
与b[0 ~ j]
的最长公共子序列的长度。
代码实现
- 暴力递归(TLE)
public int longestCommonSubsequence(String A, String B) {
if (A == null || B == null) {
return 0;
}
char[] a = A.toCharArray();
char[] b = B.toCharArray();
return findLCS(a, 0, b, 0);
}
//返回a[i ~ n] 与 b[j ~ m]的最长公共子序列的长度
public int findLCS(char[] a, int i, char[] b, int j) {
if (i == a.length || j == b.length) {
return 0;
}
if (a[i] == b[j]) {
return 1 + findLCS(a, i + 1, b, j + 1);
}else {
return Math.max(findLCS(a, i + 1, b, j), findLCS(a, i, b, j + 1));
}
}
- 记忆化搜索
public int longestCommonSubsequence(String A, String B) {
if (A == null || B == null) {
return 0;
}
char[] a = A.toCharArray();
char[] b = B.toCharArray();
int [][] memory = new int[a.length][b.length];
for(int[] nums : memory) {
Arrays.fill(nums, -1);
}
return findLCS(a, 0, b, 0, memory);
}
//返回a[i ~ n] 与 b[j ~ m]的最长公共子序列的长度
public int findLCS(char[] a, int i, char[] b, int j, int[][] memory) {
if (i == a.length || j == b.length) {
return 0;
}
if (memory[i][j] != -1) {
return memory[i][j];
}
int res = 0;
if (a[i] == b[j]) {
res = 1 + findLCS(a, i + 1, b, j + 1, memory);
}else {
res = Math.max(findLCS(a, i + 1, b, j, memory), findLCS(a, i, b, j + 1, memory));
}
memory[i][j] = res;
return res;
}
- 动态规划
public int longestCommonSubsequence(String A, String B) {
// write your code here
if (A == null || B == null ) {
return 0;
}
char[] a = A.toCharArray();
char[] b = B.toCharArray();
int n = a.length;
int m = b.length;
int[][] dp = new int[n + 1][m + 1];
//dp[i][j] : a[i ~ n] 与 b[j ~ m]的最长公共子序列的长度
for (int i = n - 1; i >= 0; --i) {
for (int j = m - 1; j >= 0; --j) {
if (a[i] == b[j]) {
dp[i][j] = 1 + dp[i + 1][j + 1];
}else {
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
}
}
}
return dp[0][0];
}