動態規劃-最長公共子序列

最長公共子序列

最長公共子序列(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
  
*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章