动态规划-最长公共子序列

最长公共子序列

最长公共子序列(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
  
*/
posted @ 2020-03-22 20:58  南风sa  阅读(148)  评论(0编辑  收藏  举报