最长公共连续子串(Longest Common Substring)

上一篇博客,介绍了最长公共子序列(Longest Common Sequence, LCS),本文介绍最长公共连续子串。与序列不同,子串要求字符是连续的,而子序列可以不连续。

下面同样用LCS表示最长公共连续子串。

首先分析一下,如果暴力求解法的时间复杂度,两个字符串A和B长度分别为x和y,则字符串的子串个数分别为

n1 = x + (x-1) + … + 1 = x(x-1) / 2

n2 = y + (y-1) + … + 1 = y(y-1) / 2

所以,暴力求解法下,对比两个子串是否相等,时间复杂度为O(x^2*y^2),即O(n^4)。

再降低一点复杂度,例如用A的每个子串去与B的同样长度的子串是对比,因此复杂度是O(x^2*y),即O(n^3),则是暴力求解法最低的复杂度了。

如果采用动态规划呢,类比最长公共子序列问题,我们需要确定dp[][]的含义,然后推导状态转移方程

子串和子序列的区别在于:连续与否。

假设,两个字符串分别为

A = a1, a2, …, ax

B = b1, b2, …, by

我们定义dp[i][j]的含义是:字符串 [a1,a2,…,ai]与字符串[b1,b2,…,bj]的最长公共连续子串的最后一个字符与这个两个字符串的最后一个字符相等的情况下,这个LCS的长度(不好理解)。因此状态转移方程为:

《最长公共连续子串(Longest Common Substring)》

注意观察,A[i]!=B[j]的情况下,dp[i][j]也等于0,这是子序列和子串在状态转移方程上的区别。

那么,我们通过两层循环,计算出dp[][]的值,然后找到其中最大的值,就是LCS的长度了。

LCS的长度对应的dp[][]的两个下标为LCS末尾字符分别在字符串A和B中的下标,可以利用这个很容易地找到LCS的具体值,这貌似是最长公共子串比最长公共子序列稍微简单的地方。

代码如下:

#include<iostream>
#include<string.h>
using namespace std;

//dp[i][j]:串(x1,x2,...,xi)与串(y1,y2,...,yj),
//d[i][j]表示这两个串结与最长公共子串结尾相同时,最长公共子串的长度

//状态转移方程如下: 
//若i=0或j=0,则dp[i][j] = 0
//否则:
//		若A[i]==B[j],则dp[i][j] = dp[i-1][j-1] + 1
//		若A[i]!=B[j],则dp[i][j] = 0


//用于打印的函数,后面才用到 
void print_substring(string str, int end, int length)
{
	int start = end - length + 1;
	for(int k=start;k<=end;k++)
		cout << str[k];
	cout << endl;
}

int main()
{
	string A,B;
	cin >> A >> B;
	int x = A.length();
	int y = B.length();
	A = " " + A;//特殊处理一下,便于编程 
	B = " " + B;
	
	//回忆一下dp[][]的含义? 
	int **dp = new int* [x+1];
	int i,j;
	for(i=0;i<=x;i++)
	{
		dp[i] = new int[y+1];
		for(j=0;j<=y;j++)
			dp[i][j] = 0;
	}
	
	
	//下面计算dp[i][j]的值并记录最大值 
	int max_length = 0;
	for(i=1;i<=x;i++)
		for(j=1;j<=y;j++)
			if(A[i]==B[j])
			{
				dp[i][j] = dp[i-1][j-1] + 1;
				if(dp[i][j]>max_length)
					max_length = dp[i][j];
			}
			else
				dp[i][j] = 0;

	
	//LCS的长度已经知道了,下面是根据这个最大长度和dp[][]的值,
	//找到对应的 LCS具体子串, 注意:可能有多个 
	int const arr_length = (x>y?x:y) + 1;
	int end_A[arr_length];	//记录LCS在字符串A中结束的位置 
	int num_max_length = 0;	//记录LCS的个数 
	
	for(i=1;i<=x;i++)
		for(j=1;j<=y;j++)
			if(dp[i][j] == max_length)
				end_A[num_max_length++] = i;
	
	cout << "the length of LCS(substring) is : " << max_length  << endl << " nums: " << num_max_length << endl << "they are (it is): " << endl;
	for(int k=0;k<num_max_length;k++)	//输出每个具体的子串 
		print_substring(A, end_A[k], max_length);
	
	return 0;
}

运行结果图:

《最长公共连续子串(Longest Common Substring)》

点赞