动态规划:洛谷 P2758 编辑距离 —— 一题多解:递归和DP求解
洛谷 P2758 编辑距离
这题是普及/提高-的,观察发现可以用二维数组DP做。
思路是建一个二维数组,dp[i][j]代表a的前i个变成b的前j个最少需要的步数。状态转移方程:
dp[i][j]=min( min (dp[i-1][j] , dp[i][j-1] )+1, dp[i-1][j-1] + flag) ;
dp[i-1][j]:表示删。表示的是前i-1个变成j的步数更少,所以不如直接删掉第i个。
dp[i][j-1]:表示增。表示的是前i个变成前j-1个步数更少,所以不如在i的后面添加一个第j个字符。
dp[i-1][j-1]:表示换。如果第i个等于第j个,那就不用换,直接dp[i][j]=dp[i-1][j-1]+flag,此时flag=1。不然就要换,就需要加一步,dp[i][j]=dp[i-1][j-1]+flag,此时flag=1。
注意点①:边界条件注意设置,dp[0][j]=j 0个字符变成j个字符要j步,dp[i][0]同理。
注意点②:因为字符串是从下标1开始,所以再创两个char数组存放数组,并让数据从下标1开始,方便后面DP计算。
可以有两种求解方式:DP和递归,这里的递归也可以叫做记忆化搜索。记忆化搜索一定能转化为DP,反之则不行。
一、DP
1 //洛谷 P2758 编辑距离 2 #include<iostream> 3 #include<cstring> 4 using namespace std; 5 char a[2002]; 6 char b[2002]; 7 string x1, x2; 8 int dp[2002][2002]; 9 int main() 10 { 11 cin >> x1; 12 cin >> x2; 13 int l1 = x1.size(); 14 int l2 = x2.size(); 15 for (int i = 0; i < l1; ++i) 16 { 17 a[i + 1] = x1[i]; 18 } 19 for (int i = 0; i < l2; ++i) 20 { 21 b[i + 1] = x2[i]; 22 } 23 for (int i = 1; i <= l1; ++i)dp[i][0] = i;//边界调节,b数组长度为0时,变成a数组要花i步 也就是增添i个 24 for (int j = 1; j <= l2; ++j)dp[0][j] = j; 25 for(int i=1;i<=l1;++i) 26 for (int j = 1; j <= l2; ++j) 27 { 28 int flag = 1; 29 if (a[i] == b[j]) 30 { 31 //如果最后一个元素相同 那么这一步就不需要加1 32 flag = 0; 33 } 34 dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1])+1, dp[i - 1][j - 1] + flag); 35 36 } 37 cout << dp[l1][l2]; 38 return 0; 39 }
二、递归
//洛谷 P2758 编辑距离 #include<iostream> #include<cstring> using namespace std; char a[2002]; char b[2002]; string x1, x2; int dp[2002][2002]; int recursion(int i, int j)//代表求a的前i个字符变成b的前j个字符需要几步 { if (dp[i][j] != -1)//如果已经算出来的 直接返回 return dp[i][j]; if (i == 0)//i等于0 需要j步 也是计算边界条件 return j; if (j == 0) return i; int flag = 1;//判断最后一个字符相等不 的标志 if (a[i] == b[j]) flag = 0; return dp[i][j] = min(min(recursion(i - 1, j) + 1, recursion(i, j - 1) + 1), recursion(i - 1, j - 1) + flag); } int main() { cin >> x1; cin >> x2; int l1 = x1.size(); int l2 = x2.size(); for (int i = 0; i < l1; ++i) { a[i + 1] = x1[i]; } for (int i = 0; i < l2; ++i) { b[i + 1] = x2[i]; } memset(dp, -1, sizeof(dp)); int ans = recursion(l1, l2); cout << ans; return 0; }
三、对比时间复杂度
DP:
递归:
显然DP的时间复杂度更小,差了两倍多,空间复杂度也更省点。