动态规划-最长公共子序列
最长公共子序列
最长公共子序列(Longest Common SubSequence)的问题描述为:
给定两个字符串A和B,求一个字符串,使得这个字符串是 A 和 B 的最长公共部分
例如:"sadstory" 和 "adminsorry" 的最长公共子序列为 "adsory",长度为 6
如果是暴力解法遍历的话……设A和B的长度分别是 n 和 m,对于两个字符串的子序列各有 2n, 2m,然后再比较是否相同又需要 O(max(m,n)) ,这样总复杂度就会达到O(2m+n x max(m, n))。
还是让我们来看看动态规划的做法。
我们使用一个数组 dp[i][j]
,表示字符串 A 的 i 号位和字符串 B 的 j 号位之前的 LCS 长度(下标从 1 开始),如 dp[4][5]
代表 "sads" 和 "admin" 的LCS长度,那么可以根据 A[i] 和 B[j] 的情况,分为两种情况:
- 如果 A[i] == B[j],那么字符串 A 和 B 的 LCS 增加了一位,即有
dp[i][j] = dp[i-1][j-1]+1
。 - 如果 A[i] != B[j],那么字符串 A 的 i 号位和 B 的 j 号位之前的 LCS 无法延长,因此
dp[i][j]
会继承dp[i-1][j]和dp[i][j-1]
中较大值,即dp[i][j] = max(dp[i-1][j],dp[i][j-1])
。
由此可以得到状态转移方程:
if(A[i] == B[j]) dp[i][j] = dp[i-1][j-1]
if(A[i] != B[j]) dp[i][j] = max(dp[i-1][j], dp[i][j-1])
其中,如果某一个字符串的长度为 0,那么 LCS 一定为 0,所以有边界:
dp[i][0] = dp[0][j] = 0
这样状态 dp[i][j]
,只与其之前的状态有关,由边界出发就可以得到整个 dp 数组,最终 dp[n][m]
就是需要的答案,时间复杂度为 O(mn)。
代码实现如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100;
char A[N], B[N]; // 存放字符串
int dp[N][N];
int main() {
int n;
gets(A + 1);
gets(B + 1);
int lenA = strlen(A + 1);
int lenB = strlen(B + 1);
// 初始化边界
for(int i = 0; i <= lenA; i++) {
dp[i][0] = 0;
}
for(int i = 0; i <= lenB; i++) {
dp[0][j] = 0;
}
// 状态转移方程
for(int i = 1; i <= lenA; i++) {
for(int j = 1; j <= lenB; 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]);
}
}
}
// dp[lenA][lenB]就是答案
printf("%d\n", dp[lenA][lenB]);
return 0;
}
/*
input:
sadstory
adminsorry
output:
6
*/