1. 程式人生 > 其它 >動態規劃:洛谷 P2758 編輯距離 —— 一題多解:遞迴和DP求解

動態規劃:洛谷 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的時間複雜度更小,差了兩倍多,空間複雜度也更省點。