最長公共子序列
最長公共子序列(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
*/