偶然看到这道经典题,顺便复习下DP,
由于懒得做图,所以,需要图片或者其他讲法,请参考这篇https://blog.csdn.net/chichoxian/article/details/53944188。 可能你需要配合这位作者的图片才能更好理解
有两个字符串str1跟str2, 设他们的最短距离为str1变为str2的操作最小次数,有以下三种操作
1. 插入字符,如abc -> abcd
2. 删除字符 ,如abc -> ac
3. 替换字符, 如abc -> adc
输入两个字符串,请计算两个字符串的最短距离
由于我们发现,一个字符串中,所有子字符串(或者说字符)都可能有以上操作,所有此问题具有重复子问题。
这是一道经典的dp算法, 设i为str1[:i], str2[:j](python表达方法,str[:i]意味着str[0]到str[i], 读者注意str[i]是一个字符,str[:i]是一个子字符串)
状态
dp[i][j]定义为str1[:i]到str2[:j]的最短距离
思路是这样的,先考虑最后一步,此时i是str1的最后一个字符,j是str2的最后一个字符, 那么有几种可能
1. str1[i] == str2[j],即最后一个字符串相等
那么不进行任何操作,就使用前面最优解, dp[i][j] = dp[i-1][j-1] + 0 = dp[i-1][j-1]
2. str1[i] != str2[j], 那么就需要进行操作,即插入,删除,替换
if len(str1) >len (str2)
那么最优解明显是删除字符str[i],则代表 dp[i][j] = dp[i][j-1] + 1(意味着dp[i][j-1]时构建的最优字符串再删除str[i]才能达到此时dp[i][j]最优解)(i > j, 那么i -1 == j, 由于我们每次只操作一个字符,所以i >j时,i-1则==j)
else if len(str1) < len(str2)
插入操作, 插入str[i], dp[i][j] = dp[i-1][j] + 1(意味着, dp[i-1][j]时构建的最优字符串,需要再插入str[i]才能达到最优解)(i < j, 那么 i+1 == j)
else len(str1) == len(str2)
替换操作,dp[i][j] = dp[i-1][j-1] + 1(意味着,dp[i-1][j-1]构建的字符串,再把str[i]变成str[j]才能达到最优解)(i==j, 那么i-1 == j-1)
由于此问题具有重复子问题,所有从最优解回到一开始,我们考虑的这几种情况会重复运用到前面的所有情况,即相当于所有长度,字符相等或者不等,我们都考虑了,并解决了(使用插入,删除,替换等), 因此可以不用考虑中间某个字符的插入,删除,替换操作怎样,因为在这个字符作为末尾的时候就已经判断过了
当然,上面的条件判断长度关系, 是没有必要的,那是搜索时用的判断条件(否则直接搜索会因为搜索广度或者深度没有上限而不法直接搜索),使用dp时我们可以每次判断都直接使用三种插入方法,并取最优解。说长度长时删除,短时插入,只是用搜索方便来理解dp
状态转移方程
即str1[i] != str2[j]时有
dp[i][j] = min{dp[i-1][j-1], dp[i][j-1], dp[i-1][j]} + 1
str1[i] == str2[j]时, 有
dp[i][j] = dp[i-1][j-1]
边界
先说结论,边界是空字符串””到str1跟str2的距离。 为什么边界是””呢,本人一开始也是用str1[0]与str2[0],之类的作为边界,发现在矩阵dp[0][j]以及dp[i][0]时需要一个个用上面搜索的方法判断的窘境,因此,最后也是发现,多使用一行空字符串的话,就能很简单解决
因此, 边界为dp[0][j] = j, 以及dp[i][0] = i, 意味着从空字符串插入一步步构建str1跟str2子字符串的过程
你可能发现了一个问题, 矩阵dp[i][j]的第0行与第0列代表着空字符串到str1的最短距离以及到str2的最短距离。这个问题会导致一个注意点—— 矩阵的dp[i][j]其实意味着str[i-1]到str[j-1]的距离(因为dp矩阵多了一行一列), 因此,所有的判断字符,应该是str1[i-1]==str2[j-1]而不是str[i]到str[j], i,j的取值范围是[0, len(str)]闭区间
下面给出代码
/*
定义状态:
dp[i][j]为字符串str1的前i个字符,到str2的前j个字符的最短距离
状态转移:
两个字符串相等时:
相等:dp[i][j] = dp[i-1][j-1]
不等时:
插入:dp[i][j] = dp[i][j-1] + 1 //此时, i < j, 使用插入的i
删除: dp[i][j] = dp[i-1][j] + 1 //此时,i > j, i-1与j达到同长
替换:dp[i][j] = dp[i-1][j-1] + 1 //此时 i == j,
边界:
从空字符串""到str,插入i次,
dp[i][0] = i
dp[0][j] = j
注意:字符串是从0开始的,而0行0列考虑的是空字符串
*/
#include<iostream>
#include<string>
#include<algorithm>
#define MAXV 256
using namespace std;
string str1, str2;
int dp[MAXV][MAXV];
int distance()
{
int len1 = str1.length();
int len2 = str2.length();
for(int i = 0; i <= len1; i++)
dp[i][0] = i;
for(int j = 0; j <= len2; j++)
dp[0][j] = j;
for(int i = 1; i <= len1; i++)
{
for(int j = 1; j <= len2; j++)
{
if(str1[i - 1] == str2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1];
}
else
{
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
}
}
}
for(int i = 0; i <= len1; i++)
{
for(int j =0; j <= len2; j++)
cout << dp[i][j] << " ";
cout << endl;
}
return dp[len1][len2];
}
int main()
{
cin >> str1 >> str2;
cout << distance() << endl;
return 0;
}