1、最长公共子序列(LCS)
题目: Common Subsequence
题目描述: 给定两个字符串A和B(或数字序列),求一个字符串,使得这个字符串是所给两个字符串的最长公共部分(可以不连续)
动态规划的做法:时间复杂度,O(n*m)
设dp[i][j]表示A的i号位和B的j号位前的LCS,我们从下标1以开始存字符串数组(如果从0开始存还要一个一个讨论边界是0还是1),初始化dp[0][0]=0,比如说A=wesd,B=wzeji,dp[2][3]=2(就是we和wze的LCS),然后我们来看dp[i][j]的转移方程,分A[i]=b[j]和a[i]!=b[j]两种情况
来源:《算法笔记》,胡凡,曾磊主编
代码:
#include <iostream>
#include <string.h>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int manx=2e3+10;
const int INF=1e9;
int dp[manx][manx];
int main()
{
char str1[manx],str2[manx];
while(scanf("%s%s",str1+1,str2+1)!=EOF)
{
int i,j;
int l1=strlen(str1+1);
int l2=strlen(str2+1);
memset(dp,0,sizeof(dp));
for(i=1; i<=l1; i++)
for(j=1; j<=l2; j++)
{
if(str1[i]==str2[j])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
printf("%d\n",dp[i-1][j-1]);
}
}
2、最长公共上升子序列(LCIS)
例题:Greatest Common Increasing Subsequence
题目描述: 在最长公共子序列的基础上,增加一个上升子序列的条件
假设和最长公共子序列一样,我们规定dp[i][j]表示A的i号位和B的j号位前的LCIS。在最长公共子序列中,当A[i]=B[j]时,我们直接用dp[i-1][j-1]+1来更新dp[i][j]。但在这里,因为有上升的限制,就不能直接通过dp[i-1][j-1]+1来得到dp[i][j]了,因为A的i-1号位和B的j-1号位前的LCIS中的最后一位数不一定 < < <A[i]。
所以为了判断能否通过前面的dp值来更新dp[i][j],我们这样规定:设dp[i][j]表示A的i号位和B的j号位前并且以B[j]为结尾的LCIS的长度。
这样当A[i]=B[j]时,我们就可以寻找最大的dp[i-1][k]来更新dp[i][j](1 ≤ \leq ≤k ≤ \leq ≤j-1,B[k] < < <B[j])
最后就剩下了怎么确定k值的问题,很容易想到我们可以枚举1~j-1中所有符合条件(B[k] < < <B[j])的dp[i-1][k]值,但这样复杂度就到了O(n^3)。所以这里我们换一个思路:在第二层循环中,我们每次从左至右都更新了dp[i][1]到dp[i][m]所有值(m是序列B的长度),即以序列A固定的前i位去匹配序列B,所以这里A[i]值是固定的,我们就可以维护一个pos值,使得dp[i-1][pos]是在条件B[pos]<A[i]下的最优解。当A[i]=B[j]时,我们就可以直接用dp[i-1][pos]+1来更新dp[i][j]。
代码实现:
ans=0;
memset(dp,0,sizeof(dp));
for(int i=1; i<=n; i++)
{
int pos=0;
for(int j=1; j<=m; j++)
{
if(a[i]>b[j]&&dp[i-1][pos]<dp[i-1][j])
pos=j;//以b[j]结尾,a[1]~a[i]可以匹配的最大长度
if(a[i]==b[j])
{
dp[i][j]=dp[i-1][pos]+1;
ans=max(ans,dp[i][j]);
}
else dp[i][j]=dp[i-1][j];
}
}
看到这里不难发现每次更新只用到了dp[i-1][]的那一层,所以dp我们可以优化成一维。
ans=0;
memset(dp,0,sizeof(dp));
for(int i=1; i<=n; i++)
{
int pos=0;
for(int j=1; j<=m; j++)
{
if(a[i]>b[j]&&dp[pos]<dp[j])
pos=j;
if(a[i]==b[j])
{
dp[j]=dp[pos]+1;
ans=max(ans,dp[j]);
}
}
}