【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题在最开始着实把我吓唬住了。但当我画出表格以后,就一步一步将题目解出来了。关键是通过自己的手动计算得到正确结果。这个正确结果的计算过程就是算法。如果一步没法得到完整的计算过程,可以先用肉眼看,写出几行,然后再分析。通过找出计算结果和肉眼看出结果的矛盾点,一步一步优化算法,从而得到最后结果。

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