LCS问题

LCS问题

1. 简介

LCS通常是指Longest Common Subsequence, 但是也可代指Longest Common Substring。子串是一种特殊的子序列,子串和子序列的区别就是字串要求是组成子串的
各字符是连续的,而子序列仅仅要求各字符的下标是递增的即可。举个例子,如helloworldworld是子串(也是子序列), hw不是子串是子序列。

LCS问题就是在若干(这里是两个)字符串中,找到最长的一个字符串p, 这个p是那些字符串的子串(子序列)。LCS问题的意义之一就是去衡量若干字符串的相似度,例如在生物信息学
中有DNA序列的问题,比如序列a = "ATGATAGATAGATAG", 序列b="TGGGCCGAGAAGCGAGA",需要一种去衡量ab相似度的方法,那么最长公共子序列就可以作为一种衡量方法。
除此之外,在软件开发领域中的版本控制系统(典型的就是Git),比较同一个文件不同时刻的差异的时候,就需要利用到这个方法。

例如下图就是Git上面的两次不同时刻对同一个文件的快照的对比图,我们可以看到,系统把两次的差异标示出来了。而原来位置、内容相同的部分没有标示。这个就可以通过
LCS问题的解决,来找到最长的公共子序列部分,就是作为公共的、未发生改变部分;剩下的就是改变的部分,应该标示出来。

这里写图片描述

同理基于这个,我们还可以简单的比较两篇文章的相似度来检查雷同、抄袭的情况,这么看来,LCS问题的还是和我们日常比较相关的。

解决LCS问题依靠暴力(brute force)的求解时间复杂度是指数级别的,因此不太现实,由于问题的本身的最优解具有最优子结构特征,因此原问题的求解可以化为多个子问题的求解;另外子问题的求解存在重叠的情况,这恰好是适合动态规划求解的问题的第二个特征:存在重叠的子问题求解过程,因此适合利用动态规划来求解,这个也是较常见的解决LCS问题的方法,时间复杂度为O(n*m),动态规划易于编写,但是时间复杂度较高;另外更高级的还存在一种线性复杂度的O(n+m)的解法,就是Suffix Tree,不过这种方法的缺点是程序难以编制。因此在日常的编码中,动态规划已经够用了。

2. 最长公共子串问题

设dp[i][j]表示s[0~i]和t[0~j]的最长公共子串的长度,对于两个字符串的最长公共子串问题的最优解,最优解存在如下结构:

dp[i][j] = 1 (i == 0 || j == 0)
dp[i][j] = dp[i-1][j-1] + 1;(s[i] == t[j] && i > 0 && j > 0);
dp[i][j] = 0;(s[i] != t[j])

上述等式建立了原问题和子问题的关系(此类证明可用反证法),剩下的就是编码了。

public static List<String> findLongestLCS(String s, String t) {
   char[] schs = s.toCharArray();
   char[] tchs = t.toCharArray();
   int m = s.length(), n = t.length();
   int[][] dp = new int[m][n];
   int maxSize = 0;
   List<Integer> indexs = new ArrayList<>();//存储最长公共子串的在原始串中结束位置

   for (int i = 0; i < m; i++)
     for (int j = 0; j < n; j++) {
       if (schs[i] == tchs[j]) {
         if (i == 0 || j &
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用动态规划空间优化的 C 代码: ```c #include <stdio.h> #include <string.h> #define MAX_N 1000 int dp[2][MAX_N+1]; int lcs(char* s1, char* s2, int len1, int len2) { int i, j; for (i = 0; i <= len1; i++) { for (j = 0; j <= len2; j++) { if (i == 0 || j == 0) { dp[i%2][j] = 0; } else if (s1[i-1] == s2[j-1]) { dp[i%2][j] = dp[(i-1)%2][j-1] + 1; } else { dp[i%2][j] = (dp[(i-1)%2][j] > dp[i%2][j-1]) ? dp[(i-1)%2][j] : dp[i%2][j-1]; } } } return dp[len1%2][len2]; } int main() { char s1[MAX_N], s2[MAX_N]; scanf("%s%s", s1, s2); int len1 = strlen(s1); int len2 = strlen(s2); printf("%d\n", lcs(s1, s2, len1, len2)); return 0; } ``` 以上代码将原来的二维数组 `dp` 改为了两个一维数组,使用滚动数组的方式实现状态转移。这样可以将空间复杂度从 O(n²) 优化为 O(n)。 以下是使用树状数组优化的 C 代码: ```c #include <stdio.h> #include <string.h> #define MAX_N 1000 int c[MAX_N+1]; int dp[MAX_N+1]; int lowbit(int x) { return x & (-x); } void update(int x, int v, int n) { while (x <= n) { if (dp[x] < v) { dp[x] = v; } x += lowbit(x); } } int query(int x) { int res = 0; while (x > 0) { if (dp[x] > res) { res = dp[x]; } x -= lowbit(x); } return res; } int lcs(char* s1, char* s2, int len1, int len2) { int i, j; for (i = 1; i <= len1; i++) { memset(c, 0, sizeof(c)); for (j = 1; j <= len2; j++) { if (s1[i-1] == s2[j-1]) { int v = query(j-1) + 1; update(j, v, len2); } } } return query(len2); } int main() { char s1[MAX_N], s2[MAX_N]; scanf("%s%s", s1, s2); int len1 = strlen(s1); int len2 = strlen(s2); printf("%d\n", lcs(s1, s2, len1, len2)); return 0; } ``` 以上代码使用了树状数组来维护前缀最长公共子序列的长度。时间复杂度为 O(nlogn)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值