编辑距离与滚动数组优化 - 二维动态规划模板
题目:编辑距离 。
思路
显然,定义 \(f[i][j]\) 表示字符串 \(a\) 中前 \(i\) 个字符到 字符串 \(b\) 中前 \(j\) 个字符的编辑距离。
那么对于 \(a_i=b_j\) 时,我们对当前位无需进行任何编辑操作,则 \(f[i][j]=f[i-1][j-1]\) 。
如果 \(a_i \ne b_j\) ,那么我们就要对当前位进行编辑:
- 对于修改操作,我们先要保证 字符串 \(a\) 中前 \(i-1\) 个字符 已经编辑到了 字符串 \(b\) 中前 \(j-1\) 个字符,接下来才把 \(a_i\) 修改成 \(b_j\) ,所以转移为 \(f[i][j]=f[i-1][j-1]+1\) 。
- 对于插入操作,因为我们是向 \(a_i\) 后面插入一个 \(b_j\) ,所以我们要保证 字符串 \(a\) 中前 \(i\) 个字符 已经编辑到了 字符串 \(b\) 中前 \(j-1\) 个字符,再同时往这两个字符串后插一个 \(b_j\) ,所以转移为 \(f[i][j]=f[i][j-1]+1\) 。
- 对于删除操作,由于我们是删除 \(a_i\) 字符,所以我们要保证 字符串 \(a\) 中前 \(i-1\) 个字符 已经编辑到了 字符串 \(b\) 中前 \(j\) 个字符,这样才能删掉 \(a_i\) ,所以方程为 \(f[i][j]=f[i-1][j]+1\) 。
这三种情况取一个 min 即可。
注意初始化,\(f[0][0]=0,f[i][0]=i,f[0][j]=j\) ,因为分别要进行这么多次的删除和插入。
#include <bits/stdc++.h>
using namespace std;
string a,b;
int f[2005][2005];
int main()
{
cin>>a>>b;
memset(f,0x3f,sizeof(f));
f[0][0]=0;
for(int i=1;i<=a.length();i++)f[i][0]=i;
for(int i=1;i<=b.length();i++)f[0][i]=i;
for(int i=1;i<=a.length();i++)
{
for(int j=1;j<=b.length();j++)
{
if(a[i-1]==b[j-1])f[i][j]=f[i-1][j-1];
else f[i][j]=min(f[i][j],min(f[i-1][j-1]+1,min(f[i-1][j]+1,f[i][j-1]+1)));
}
}
cout<<f[a.length()][b.length()];
return 0;
}
滚动数组优化
观察到,如果我们把 dp 数组看作一个网格,那么某一个格子的状态一定是由它左边、上边、左上角的格子的状态转移过来的。
因此,我们就可以用滚动数组优化。
但这里的滚动数组不能用像背包一样的倒序转移,因为这里的转移会用到同级的状态:格子左侧。因此我们先要更新格子左边,才能更新这个格子。所以得从左到右转移。
但从左到右转移会覆盖掉左上角的内容,因此我们要把左上角的东西单独记录一下,然后在赋初值的时候注意一下 \(f[0]=i\) 即可。
#include <bits/stdc++.h>
using namespace std;
string a,b;
int f[2005];
int main()
{
cin>>a>>b;
f[0]=0;
for(int i=0;i<=b.length();i++)f[i]=i;
for(int i=1;i<=a.length();i++)
{
int zs=f[0];
f[0]=i;
for(int j=1;j<=b.length();j++)
{
int tmp=zs;
zs=f[j];
if(a[i-1]==b[j-1])f[j]=tmp;
else f[j]=min(tmp+1,min(f[j]+1,f[j-1]+1));
}
}
cout<<f[b.length()];
return 0;
}