【LeetCode】劍指DP:72. Edit Distance 修改字符串

一、概述

只看這個題目,修改字符串,感覺很抽象,不好理解。我也想不到什麼好的。先描述一下:

輸入兩個字符串word1和word2,我們的目標是,把word1轉化成word2。我們有三個技能:

第一個:把一個字符變成另一個字符;

第二個:刪去一個字符;

第三個:添加一個字符。

目標就是費最少的藍,用最少的技能轉化完成。比如說我輸入的word1是“horse”,word2是“ros”。

那麼,先用技能二,刪去h變成orse;然後用技能二,刪去o變成rse;然後用技能二,刪去e變成rs;然後用技能三,插入o變成ros。一共用了四次技能——然而這不是最少的。

最少的應該是:先用技能一,把h轉化爲r變成rorse;然後用技能二,刪去第二個r變成rose;然後用技能二,刪去e變成ros。

有點複雜。

二、分析

我第一眼看到這個題目是一臉懵逼的。不知道怎麼做。然後着手去驗證了一下樣例,發現我得到的是4而不是3。然後就卡住了:手動都做不出,那還談何想算法。

然後我敏銳的思維告訴我,丫的這題八成是DP。

DP?怎麼DP啊?雖說兩個字符串互相搞一搞,最適合DP做,但是我現在還是一頭霧水。

怎麼辦呢?先在紙上寫一寫吧。

  h o r s e
r          
o          
s          

往裏面填什麼呢?呆住了。

先看看座標爲(0,0)的。對應的是h和r。我們嘗試給這個格子代表的元素下一個定義:

這個格子代表h想變成r,需要用幾次技能。

很明顯,用一次一技能。

那(0,1)呢?代表o變成r用幾次技能麼?那也沒意義啊,一個字符變成一個字符,要是一樣就不變;不一樣就用一次一技能就完事,這表格有個卵子用。

看來不是o變成r。那看成ho變成r怎麼樣?那就是兩次技能,一次技能二一次技能一。要是這樣,那好像就有點意思了:第一行的第i個格子代表word1的前i個元素需要使用多少次技能變成word2的第1個元素。

這好像就有用了。先把第一行做完。

  h o r s e
r 1 2 2 3 4
o          
s          

然後看第二行。(1,0)代表什麼?代表h怎麼變成o麼?不是的,這和DP的小問題推進爲大問題相悖。這代表h怎麼變成ro。需要兩次技能。那ho變成ro呢?一次技能啊。這樣我們可以寫下第二行。

  h o r s e
r 1 2 2 3 4
o 2 1 2 3 4
s          

等一下,我們肉眼看完了第二行的結果。但是還沒找到規律。來看看:

(1,1)爲1,爲什麼爲1?因爲只要用一次技能。那這個1通過表格上的數據能不能推出來?我們看一下:

(0,0)表示h需要一次技能變成r,(1,1)表示ho需要1次技能變成ro。爲什麼是一次呢?因爲o=o嘛。還挺可愛。

這樣我們就找到了一個遞推式子:DP[i][j]=DP[i-1][j-1]+word1[j]==word2[i]?0:1

這個式子原理是這樣的:我想知道ho如何變成ro,我現在已經知道h如何變成r,只需要知道o如何變成o就可以了。

這樣就把一個複雜的問題變簡單了。再看(1,2)我想知道hor怎麼變成ro,我已經知道ho變成r需要兩次技能,現在r變成o需要一次技能,因此一共應該是三次啊,怎麼是兩次呢?

來看看哪裏錯了:我們用上面的DP式子,是默認兩個子串hor和ro中,ho與r對應,r和o對應;但真的要這麼對應麼?這麼對應,那麼只會用到技能一啊。爲什麼只用到技能一?因爲r和o不一樣,所以要用技能一轉換成一樣的;要是一樣,就不轉換。按我們先前的思路,就相當於只用了這一個技能。所以不對了。

那麼如何修改思路,使得技能二和技能三也能用呢?

那就得分析爲什麼(1,2)等於2了。在hor和ro中,我們讓兩個o對應,那麼hor的h對應ro的r;hor的r沒人對應,就得用技能二刪去。噢~這樣我們就用到技能二了。關鍵就在於並不一定是兩個子串的最後一個對應。如何在公式中體現這一點呢?

我們來看,讓兩個o對應,讓h和r對應,本質上就是ho對應ro,這對應DP[i][j-1],那麼我們的結果就是DP[i][j-1]+1,加的這個1是用一次技能二。

現在公式變成這樣:

DP[i][j]=min(DP[i-1][j-1]+word1[j]==word2[i]?0:1,DP[i][j-1]+1)

然後開始填第三行:

  h o r s e
r 1 2 2 3 4
o 2 1 2 3 4
s 3 2 2 2 3

等一下,爲什麼(2,1)這個地方是2,根據我們上面的公式,s和o不相等,那麼是2+1和3+1比啊。怎麼也不是2。

因爲我們在公式2中沒用過技能三啊,公式還是殘缺的,所以不對。

這個2怎麼來的呢?我們要想讓ho變成ros,就讓ho變成ro,然後在後面加上s,這樣一次技能一,一次技能三得到ros。公式中沒體現這個。怎麼體現?

這時ho和ro匹配,用的是DP[i-1][j],我們的結果就是DP[i-1][j]+1,這個加的1就是用一次技能三。公式變成這樣:

DP[i][j]=min(DP[i-1][j-1]+word1[j]==word2[i]?0:1,DP[i][j-1]+1,DP[i-1][j]+1)

這下公式把三種技能都包括了。完美。

於是我們就得到了最後的DP公式。代碼如下:

class Solution {
public:
    int minDistance(string word1, string word2) {
        if(word1.size()==0)
            return word2.size();
        if(word2.size()==0)
            return word1.size();
        vector<vector<int>> DP(word2.size(), vector<int>(word1.size(), 0));
        DP[0][0]=word1[0]==word2[0]?0:1;
        int flag=word1[0]==word2[0];
        for(int i=1;i<word1.size();++i)
        {
            flag=flag==0?word1[i]==word2[0]:1;
            DP[0][i]=i+1-flag;
        }
        flag=word1[0]==word2[0]?1:0;
        for(int j=1;j<word2.size();++j)
        {
            for(int i=0;i<word1.size();++i)
            {
                if(i==0)
                {
                    flag=flag==0?word1[i]==word2[j]:1;
                    DP[j][i]=j+1-flag;
                }
                else
                {
                    DP[j][i]=min(DP[j-1][i]+1,DP[j][i-1]+1);
                    int tmp=word1[i]==word2[j]?0:1;
                    DP[j][i]=min(DP[j][i],DP[j-1][i-1]+tmp);
                }
            }
        }
        return DP[word2.size()-1][word1.size()-1];
    }
};

三、總結

這道DP題在最開始着實把我嚇唬住了。但當我畫出表格以後,就一步一步將題目解出來了。關鍵是通過自己的手動計算得到正確結果。這個正確結果的計算過程就是算法。如果一步沒法得到完整的計算過程,可以先用肉眼看,寫出幾行,然後再分析。通過找出計算結果和肉眼看出結果的矛盾點,一步一步優化算法,從而得到最後結果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章