有两个字符串,串T为ABCBDAB,而串S为BDCABA,求最长公共子串的长度:
首先我们分析,如果有两个字符Ti和Sj,就有以下公式:
dp[i][j]=dp[i-1][j-1]+1; T[i]==S[j];
dp[i][j]=max(dp[i-1][j],dp[i][j-1]); T[i]!=S[j];
可以带本题例子证出,这里就不说明了。
#include
#include
#include
using namespace std;
const int MAXN=1e3;
char a[MAXN],b[MAXN];
int dp[MAXN][MAXN];
void LCS(int n,int m)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dp[i][j]=a[i]==b[j]?dp[i-1][j-1]+1:max(dp[i-1][j],dp[i][j-1]);
}
int main()
{
scanf("%s%s",a+1,b+1);
int len1=strlen(a+1);
int len2=strlen(b+1);
LCS(len1,len2);
printf("%d\n",dp[len1][len2]);
return 0;
}
现在我们来分析一下复杂度,时间复杂度为O(n²),而空间复杂度为n*m;当然还有一种降低空间复杂度的方法也就是滚动数组,还不会这种方法就不在这里说明了,感兴趣的读者可以去搜一下,下面我们来讲一讲LCS的应用。
一个原字符串T,变为一个目标串S,可以进行以下三种操作:
(1):将T(i)变为另一个字符
(2):将T(i)删除
(3):增加一个字符
每进行一次操作编译距离加一。问最小的编译距离为多少?
例如:将"ALGORITHM" 变为"ALTRUISTIC"
A L G O R I T H M
A L T R U I S T I C
我们可以看到这个跟LCS很类似,我们这里采用线性dp来解决。首先分析,T串和S串的最后一个字符,只有三种情况(空字符,字符),(字符,字符),(字符,空字符),所以:
第一种情况:dp[i][j]=dp[i][j-1]+1;
第二种情况:dp[i][j]=T[i]==T[j]?dp[i-1][j-1]:dp[i-1][j-1]+1;
第三种情况:dp[i][j]=dp[i-1][j]+1;
#include //编辑距离
#include
#include
using namespace std;
const int MAXN=1e3+5;
char a[MAXN],b[MAXN];
int dp[MAXN][MAXN];
int LCS()
{
int lena=strlen(a);
int lenb=strlen(b);
for(int i=1;i<=lena;i++) dp[i][0]=i;//第一行,也就是一个字符串和空串肯定就是本身的操作次数
for(int i=1;i<=lenb;i++) dp[0][i]=i;//第一列同理
for(int i=1;i<=lena;i++)
{
for(int j=1;j<=lenb;j++)
{
if(a[i-1]==b[j-1]) dp[i][j]=dp[i-1][j-1];
else dp[i][j]=min(min(dp[i-1][j],dp[i-1][j-1]),dp[i][j-1])+1;
}
}
return dp[lena][lenb];
}
int main()
{
scanf("%s%s",&a,&b);
printf("%d\n",LCS());
return 0;
}
说了最长公共子串,接下来我们谈谈最长上升子序列,也是LCS的扩展;
给定一个数组大小n,随后跟着n个数,问这n个数中最长上升子序列长度为多少;
例如:
5
3 5 1 2 4
假如说我们用LCS来做,先给这个数组从小到大排序;
3 5 1 2 4
1 2 3 4 5
他们的长公共子序列为1 2 4 ,所以我们可以这样来求;
#include
#include
using namespace std;
const int MAXN=1e3;
int a[MAXN],b[MAXN];
int dp[MAXN][MAXN];
void LCS(int n)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dp[i][j]=a[i]==b[j]?dp[i-1][j-1]+1:max(dp[i-1][j],dp[i][j-1]);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
LCS(n);
printf("%d\n",dp[n][n]);
return 0;
}
然后我们来分析一下复杂度,空间复杂度为n²,时间复杂度也为n²,那我们可不可以优化一下哎,当然是可以的;接下来就是优化算法。
#include
#include
using namespace std;
const int MAXN=1e5;
int arr[MAXN],dp[MAXN];
int LIS(int n)
{
int ans=1;
for(int i=0;i<n;i++)
{
dp[i] = 1;
for(int j=0;j<i;j++)
if(arr[i]>arr[j])
dp[i]=max(dp[i],dp[j]+1);
ans=max(ans,dp[i]);
}
return ans;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&arr[i]);
printf("%d\n",LIS(n));
return 0;
}
#include
using namespace std;
const int MAXN=1e5;
int arr[MAXN],lis[MAXN];//lis 代表最长上升子序列长度
int len;
int _find(int x)
{
int left=0,right=len;
while(left<right)
{
int middle=(left+right)>>1;
if(lis[middle]<x) left=middle+1;
else right=middle;
}
return left;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&arr[i]);
lis[len]=arr[0];
for(int i=0;i<n;i++)
{
if(arr[i]>lis[len])
lis[++len]=arr[i];
else
{
int k=_find(arr[i]);
lis[k]=arr[i];
}
}
printf("%d\n",len+1);
return 0;
}
这个算法借助到了二分的知识来找到第一个比它大的数(使得能找出更长的子序列),lis 数组里的数代表的是最长上升子序列长度的最后一个数,数组长度就是最长上升子序数的长度(注意,lis数组里的数不是最长上升子序数的所有数)。两种情况,如果这个数比lis数组里的数大,直接加入lis数组,len++;否则二分找他第一个比它大的数,替换它。