题:给定两个字符串X,Y,求二者最长的公共子串,例如X=[aaaba],Y=[abaa]。二者的最长公共子串为[aba],长度为3。
1 基本算法
其实对于最长公共子串,还是比较简单易想的,因为子串是连续的,这就方便了很多。
最直接的方法就是用X每个子串与Y的每个子串做对比,求出最长的公共子串。
2 DP方案
既然最长公共子串是最长公共子序列的变体,那么最长公共子串是不是也可以用动态规划来求解呢?
我们还是像之前一样“从后向前”考虑是否能分解这个问题,在最大子数组和中,
我们也说过,对于数组问题,可以考虑“如何将arr[0,...i]的问题转为求解arr[0,...i-1]的问题”,
类似最长公共子序列的分析,这里,我们使用dp[i][j]表示 以x[i]和y[j]结尾的最长公共子串的长度,
因为要求子串连续,所以对于X[i]与Y[j]来讲,它们要么与之前的公共子串构成新的公共子串;要么就是不构成公共子串。
故状态转移方程
X[i] == Y[j],dp[i][j] = dp[i-1][j-1] + 1
X[i] != Y[j],dp[i][j] = 0
对于初始化,i==0或者j==0,如果X[i] == Y[j],dp[i][j] = 1;否则dp[i][j] = 0。
#include<stdio.h> #include<string.h> void outputLCS(char * X); // 最长公共子串 Longest Common Substring int maxlen; //记录最大公共子串长度 int maxindex; // 记录最大公共子串在串1的起始位置 void outputLCS(char * X); // 输出LCS // 最长公共子串 基本算法 =================== int comlen(char * p, char * q) { int len = 0; while(*p && *q && *p++ == *q++) { ++len; } return len; } void LCS_base(char * X, int xlen, char * Y, int ylen) { for(int i = 0; i < xlen; ++i) { for(int j = 0; j < ylen; ++j) { int len = comlen(&X[i],&Y[j]); if(len > maxlen) { maxlen = len; maxindex = i; } } } outputLCS(X); } // 最长公共子串 DP ====================================== int dp[30][30]; void LCS_dp(char * X, int xlen, char * Y, int ylen) { maxlen = maxindex = 0; for(int i = 0; i < xlen; ++i) { for(int j = 0; j < ylen; ++j) { if(X[i] == Y[j]) { if(i && j)//i>0 && j>0 { dp[i][j] = dp[i-1][j-1] + 1; } if(i == 0 || j == 0) { dp[i][j] = 1; } if(dp[i][j] > maxlen) { maxlen = dp[i][j]; maxindex = i + 1 - maxlen; } } } } outputLCS(X); } //============ common code ============================= void outputLCS(char * X) { if(maxlen == 0) { printf("NULL LCS "); return; } printf("The len of LCS is %d ",maxlen); int i = maxindex; while(maxlen--) { printf("%c",X[i++]); } printf(" "); } void main() { char X[] = "aaaba"; char Y[] = "abaa"; /* 基本算法 */ LCS_base(X,strlen(X),Y,strlen(Y)); /* DP算法 */ LCS_base(X,strlen(X),Y,strlen(Y)); }