假设存在两个相同长度平凡的序列,我们希望找到它们最长的公共子序列,在没有其他特殊条件的情况下,利用动态规划计算的时间复杂度为 $O(n^2)$ ,并且可以记录这个子序列
考虑两个指针作用于两个序列上,记 $dp_{i,j}$ 表示为连续子序列 $[a_1,a_i]$ 与 $[b_1,b_j]$ 上的最长公共子序列的长度
两个指针当前指向的元素存在两种可能性:
$a_i=b_j$ ,说明 $a_i,b_j$ 都能存在于要找的公共子序列中,状态转移为
$$ dp_{i,j}=dp_{i-1,j-1}+1 $$
$a_i\ne b_j$ ,有两种可能
- $a_i$ 不在公共子序列中,$dp_{i,j}=dp_{i-1,j}$
- $b_j$ 不在公共子序列中,$dp_{i,j}=dp_{i,j-1}$
两种可能合并后,当前的 $dp_{i,j}$ 取最优解,状态转移为
$$ dp_{i,j}=max(dp_{i-1,j},dp_{i,j-1}) $$
最后考虑边界情况,即当 $i=0,j=0$ 时,$dp_{i,j}=0$ ,这意味着一个序列没有元素时无法找出公共子序列
dp[0][0]=0;
for (int i=1;i<=m;i++){
for (int j=1;j<=n;j++){
if (a[i]==b[j]) f[i][j]=f[i-1][j-1]+1;
else f[i][j]=max(f[i][j-1],f[i-1][j]);
}
}
如果需要记录所选的最长公共子序列,则要添加一个前驱数组 $p$,表明状态的转移方向
for (int i=1;i<=m;i++){
for (int j=1;j<=n;j++){
if (a[i-1]==b[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
p[i][j]=1;
}else if (f[i][j-1]>f[i-1][j]){
f[i][j]=f[i][j-1];
p[i][j]=2;
}else{
f[i][j]=f[i-1][j];
p[i][j]=3;
}
}
}
将两个序列拆分后,分别为表格的长宽
$p=1,2,3$ 分别表示状态转移的方向,左上方,左边,上边
最后通过依次倒序访问 $p$ 数组,根据 $p_{i,j}$ 移动游标,得到公共子序列
int i=m,j=n,k=dp[m][n];
vector<char> s(k+10);
while (i>0&&j>0){
if (p[i][j]==1){
s[k--]=a[i-1];//记录公共子序列
i--;
j--;
}else if (p[i][j]==2)
j--;//移动游标
else
i--;
}
for (int i=1;i<=k;i++)
cout<<s[i];
评论区(暂无评论)