给定两个长度为x1的字符串X和长度为y1的字符串Y,找到两个字符串中从左到右(但不一定在连续块中)出现的最长字符序列。
例如,如果X = ABCBDAB且Y = BDCABA,则LCS(X,Y)= {“BCBA”,“BDAB”,“BCAB”}和LCSlength为4.
我使用标准解决方案来解决这个问题:
if(X[i]=Y[j]) :1+LCS(i+1,j+1)
if(X[i]!=Y[j]) :LCS(i,j+1) or LCS(i+1,j), whichever is greater
然后我使用了记忆,使其成为标准的DP问题。
#include<iostream>
#include<string>
using namespace std;
int LCS[1024][1024];
int LCSlen(string &x, int x1, string &y, int y1){
for(int i = 0; i <= x1; i++)
LCS[i][y1] = 0;
for(int j = 0; j <= y1; j++)
LCS[x1][j] = 0;
for(int i = x1 - 1; i >= 0; i--){
for(int j = y1 - 1; j >= 0; j--){
LCS[i][j] = LCS[i+1][j+1];
if(x[i] == y[j])
LCS[i][j]++;
if(LCS[i][j+1] > LCS[i][j])
LCS[i][j] = LCS[i][j+1];
if(LCS[i+1][j] > LCS[i][j])
LCS[i][j] = LCS[i+1][j];
}
}
return LCS[0][0];
}
int main()
{
string x;
string y;
cin >> x >> y;
int x1 = x.length() , y1 = y.length();
int ans = LCSlen( x, x1, y, y1);
cout << ans << endl;
return 0;
}
运行here,这是我在SPOJ中使用的解决方案,我超出了时间限制和/或运行时错误。
尚未接受14个用户解决方案。有没有更智能的技巧来减少这个问题的时间复杂性?
答案 0 :(得分:4)
LCS是一个经典的,经过充分研究的计算机科学问题,对于具有两个序列的情况,已知它的下界是O(n·m)。
此外,您的算法实现没有明显的效率错误,因此它应该尽可能快地运行(尽管使用动态大小的2D矩阵而不是超大的2D矩阵可能是有益的,它占用4 MiB的内存,并且需要经常缓存失效(这是一项代价高昂的操作,因为它会导致从主内存到处理器缓存的转移,这比缓存内存访问慢几个数量级。)
就算法而言,为了降低理论界限,您需要利用输入结构的细节:例如,如果您反复搜索其中一个字符串,则可能需要构建一个搜索索引来进行一些处理时间,但会使实际搜索更快。其中两个经典变体是suffix array和suffix tree。
如果已知至少有一个字符串非常短(<64个字符),则可以使用Myers’ bit vector algorithm,其执行速度要快得多。不幸的是,算法实现起来远非微不足道。存在an implementation in the SeqAn library,但使用库本身有一个陡峭的学习曲线。
(有趣的是,该算法在生物信息学中经常应用,并已在人类基因组计划的序列组装过程中使用。)
答案 1 :(得分:2)
虽然由于超出时间限制我仍然没有获得AC,但我能够实现线性空间算法。如果有人想看,这里是Hirschbirg算法的c ++实现。
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
using namespace std;
int* compute_help_table(const string & A,const string & B);
string lcs(const string & A, const string & B);
string simple_solution(const string & A, const string & B);
int main(void) {
string A,B;
cin>>A>>B;
cout << lcs(A, B).size() << endl;
return 0;
}
string lcs(const string &A, const string &B) {
int m = A.size();
int n = B.size();
if (m == 0 || n == 0) {
return "";
}
else if(m == 1) {
return simple_solution(A, B);
}
else if(n == 1) {
return simple_solution(B, A);
}
else {
int i = m / 2;
string Asubstr = A.substr(i, m - i);
//reverse(Asubstr.begin(), Asubstr.end());
string Brev = B;
reverse(Brev.begin(), Brev.end());
int* L1 = compute_help_table(A.substr(0, i), B);
int* L2 = compute_help_table(Asubstr, Brev);
int k;
int M = -1;
for(int j = 0; j <= n; j++) {
if(M < L1[j] + L2[n-j]) {
M = L1[j] + L2[n-j];
k = j;
}
}
delete [] L1;
delete [] L2;
return lcs(A.substr(0, i), B.substr(0, k)) + lcs(A.substr(i, m - i), B.substr(k, n - k));
}
}
int* compute_help_table(const string &A, const string &B) {
int m = A.size();
int n = B.size();
int* first = new int[n+1];
int* second = new int[n+1];
for(int i = 0; i <= n; i++) {
second[i] = 0;
}
for(int i = 0; i < m; i++) {
for(int k = 0; k <= n; k++) {
first[k] = second[k];
}
for(int j = 0; j < n; j++) {
if(j == 0) {
if (A[i] == B[j])
second[1] = 1;
}
else {
if(A[i] == B[j]) {
second[j+1] = first[j] + 1;
}
else {
second[j+1] = max(second[j], first[j+1]);
}
}
}
}
delete [] first;
return second;
}
string simple_solution(const string & A, const string & B) {
int i = 0;
for(; i < B.size(); i++) {
if(B.at(i) == A.at(0))
return A;
}
return "";
}
正在运行here。
答案 2 :(得分:1)
如果两个字符串共享一个公共前缀(例如“ABCD”和“ABXY”共享“AB”),则该字符串将成为LCS的一部分。普通后缀也是如此。因此,对于某些字符串对,您可以在启动DP算法之前跳过最长公共前缀和最长公共后缀来获得一些速度;这不会改变最坏情况的界限,但它会将最佳情况复杂度改为线性时间和恒定空间。