關(guān)于兩個(gè)字符串的匹配轉(zhuǎn)化問題

最近遇到好幾個(gè)這種類型的問題,主要就是給你兩個(gè)字符串,然后進(jìn)行字符串自己的匹配或者轉(zhuǎn)化,這類問題就是采用動(dòng)態(tài)規(guī)劃,二維的和一維的,現(xiàn)在對(duì)這一類問題做一個(gè)總結(jié)

題目一[leetcode72]https://leetcode.com/problems/edit-distance/

給了兩個(gè)字符串word1和word2,使用刪除,添加,替換操作從word1轉(zhuǎn)化到word2,每個(gè)操作代價(jià)為1,需要找到最小代價(jià)的轉(zhuǎn)化方法。

算法原理&算法步驟

方法一:二維dp

使用一個(gè)二維的數(shù)組dp[][],其中dp[i+1][j+1]表示word1[0,i]轉(zhuǎn)化到word2[0,j]需要的最小代價(jià),那么現(xiàn)在來求解動(dòng)態(tài)規(guī)劃方程

考慮現(xiàn)在word1[i]和Word2[j],
如果word1[i]=word2[j],那么dp[i+1][j+1]=dp[i][j]
否則word1[i]!=word2[j]時(shí),有這么幾種情況
1. abcd abce 替換 dp[i+1][j+1]=dp[i][j]
2.abc abcd 添加 dp[i+1][j+1]=dp[i+1][j]
3.adcd abd 刪除 dp[i+1][j+1]=dp[i][j+1]

有了上述動(dòng)態(tài)規(guī)劃的方程,接下來的問題就簡(jiǎn)單多了
需要特別注意的是dp[][]數(shù)組的第一行和第一列的初始化
第一行表示word1取了"",那么dp[0][j+1]=j+1(添加操作)
第一列表示word2去了"",那么dp[i+1][0]=i+1(刪除操作)

代碼:

public class Solution {
    public int minDistance(String word1, String word2) {
        int len1=word1.length();
        int len2=word2.length();
        int[][] dp=new int[len1+1][len2+1];//dp[i+1][j+1]表示word1[0,i],word2[0,j]的cnovert需要進(jìn)行的編輯次數(shù)
        
        for(int j=0;j<len2;j++){
            dp[0][j+1]=j+1;
        }
        for(int i=0;i<len1;i++){
            dp[i+1][0]=i+1;
        }
        for(int i=0;i<len1;i++){
            for(int j=0;j<len2;j++){
                if(word1.charAt(i)==word2.charAt(j)){
                    dp[i+1][j+1]=dp[i][j];
                }
                else{
                    dp[i+1][j+1]=Math.min(dp[i][j],Math.min(dp[i][j+1],dp[i+1][j]))+1;
                                         //abcc abcd        abcd abc    abc abcd
                }
            }
        }
        return dp[len1][len2];
    }
}

方法二:一維dp

現(xiàn)在回過頭來看看二維動(dòng)態(tài)規(guī)劃方程,

dp[i+1][j+1]=dp[i][j],word1[i]=word2[j]
dp[i+1][j+1]=min(dp[i][j],dp[i+1][j],dp[i][j+1])

dp[i+1][j+1]只與dp[i][j]、dp[i+1][j]、dp[i][j+1]有關(guān),由于計(jì)算順序也是一行一行的來,所以可以考慮進(jìn)行復(fù)用,只使用一個(gè)一維的數(shù)組dp,但是假設(shè)在計(jì)算j-1的時(shí)候就更新了dp[j ],那么 上一行的dp[j]就不見了,在計(jì)算dp[j+1]的時(shí)候是需要上一行的dp[j]的,所以這里之前先不更新,在計(jì)算了dp[j+1]之后再去更新dp[j],并且用一個(gè)變量prev記憶dp[j],這樣

dp[j+1]=dp[j],word1[i]=word2[j]
dp[j+1]=min(dp[j],dp[j+1],prev)  word1[i]!=word2[j],
其中dp[j+1]代表當(dāng)前行j位置,dp[j]代表上一行j-1位置,prev代表當(dāng)前行j-1位置

也是需要注意的是,word1取""的時(shí)候,也就是dp最開始的初始化
還有word2取"",也就是求解每一行的時(shí)候開始的時(shí)候的prev的初始化。
另外一點(diǎn),由于dp[j+1]是在下一列才更新,所以最后一列在循環(huán)中沒有得到更新,在一行計(jì)算完成后,需要給dp[word2.length]=prev單獨(dú)賦值

public class Solution {
    public int minDistance(String word1, String word2) {
        int len1=word1.length();
        int len2=word2.length();
        int[] dp=new int[len2+1];
        for(int j=0;j<len2;j++){
            dp[j+1]=j+1;
        }
        int prev;
        int cur;
        for(int i=0;i<len1;i++){
            prev=i+1;
            for(int j=0;j<len2;j++){
                if(word1.charAt(i)==word2.charAt(j))
                    cur=dp[j];
                else{
                    cur=Math.min(dp[j+1],Math.min(dp[j],prev))+1;
                }
                dp[j]=prev;
                prev=cur;
            }
            dp[len2]=prev;
        }
        return dp[len2];
    }
}

題目二

[leetcode10]https://leetcode.com/problems/regular-expression-matching/
表達(dá)式匹配,給了字符串s和p,需要對(duì)s和p進(jìn)行匹配,其中p的"."表示任意一個(gè)字符,p中的"*",表示0個(gè)或多個(gè)前邊的字符

算法原理&算法步驟

同樣是使用一個(gè)二維動(dòng)態(tài)規(guī)劃數(shù)組dp[][],其中dp[i+1][j+1]表示s[0,i],p[0,j]時(shí)候能夠匹配,那么現(xiàn)在來求解動(dòng)態(tài)規(guī)劃方程

假如s[i]==p[j]||p[j]=='.'  那么 dp[i+1][j+1]=dp[i][j]
否則,如果p[j]=='*',如果s[i]!=p[j-1]&&p[j-1]!='.',直接拋棄a*,dp[i+1][j+1]=dp[i+1][j-1]
      否則,表明當(dāng)前位置p s不同,前一個(gè)位置可以匹配,那么有以下幾種可能
            abc abc* 直接拋棄* dp[i+1][j+1]=dp[i+1][j]
            abcd abc* *代表一個(gè)或多個(gè)字符 dp[i+1][j+1]=dp[i][j+1]
            abcccc abc* *把前邊一個(gè)也拿走 dp[i+1][j+1]=dp[i+1][j-1]                                   

至此,動(dòng)態(tài)規(guī)劃方程已經(jīng)得到
同樣第一行的初始化,代表s為""p只有為aA這樣的才可以匹配。
而第一列的話,如果p為空的話是一定不能匹配的,所以默認(rèn)為false.

代碼:

public class Solution {
    public boolean isMatch(String s, String p) {
        if(s==null||p==null)
            return false;
        boolean[][] dp=new boolean[s.length()+1][p.length()+1];//dp[i][j]表示以i-1為結(jié)尾位置的s子串和以j-1為結(jié)尾位置的p子串是否能夠match
        dp[0][0]=true;
        for(int j=0;j<p.length();j++){//s取出子串為“”,如果p  1*2*3*這樣子類型就能夠匹配
            if(p.charAt(j)=='*'&&dp[0][j-1])
                dp[0][j+1]=true;
        }
        for(int i=0;i<s.length();i++){
            for(int j=0;j<p.length();j++){
                if(p.charAt(j)=='.'||p.charAt(j)==s.charAt(i)){
                    dp[i+1][j+1]=dp[i][j];    
                }
                else if(p.charAt(j)=='*'){
                    if(p.charAt(j-1)!='.'&&p.charAt(j-1)!=s.charAt(i)){//直接丟棄前邊一個(gè)字符  a*之間被丟棄
                        dp[i+1][j+1]=dp[i+1][j-1];
                    }
                    else{
                    //aaa a*
                        dp[i+1][j+1]=dp[i+1][j]||dp[i+1][j-1]||dp[i][j+1];//abc abc* abcc* 
                        //a*          當(dāng)做a        當(dāng)做“”        當(dāng)做多個(gè)a(注意這里不能是dp[i][j]這樣的話就只是當(dāng)做了一個(gè)a)
                    }
                }
            }
        }
        return dp[s.length()][p.length()];
    }
}

第三題

[leetcode44]https://leetcode.com/problems/wildcard-matching/
同上題大同小異,這次"?"代表任意一個(gè)字符,"*"代表任意一個(gè)字符序列,可以為空。

方法一:dp解法

和上一題一樣的思路,而且分析比上一題簡(jiǎn)單多,所以這里不再詳述

public class Solution {
    public boolean isMatch(String s, String p) {
        if(s==null||p==null)
            return false;
        boolean[][] dp=new boolean[s.length()+1][p.length()+1];
        dp[0][0]=true;
        for(int j=0;j<p.length();j++){
            if(p.charAt(j)=='*'&&dp[0][j])
                dp[0][j+1]=true;
        }
        for(int i=0;i<s.length();i++){
            for(int j=0;j<p.length();j++){
                if(p.charAt(j)==s.charAt(i)||p.charAt(j)=='?')
                    dp[i+1][j+1]=dp[i][j];
                else if(p.charAt(j)=='*'){
                    dp[i+1][j+1]=dp[i+1][j]||dp[i][j+1];
                    //            直接去除*   *代替一個(gè)或多個(gè)其他字符
                }
            }
        }
        return dp[s.length()][p.length()];
    }
}

方法二:

由于這里和前邊的元素沒有關(guān)系了,所以如果前面出現(xiàn)了不匹配,那么直接就可以判定不匹配,不需要就行后邊的操作,所以直觀上,上述動(dòng)態(tài)規(guī)劃的方法就存在很大的時(shí)間浪費(fèi),這里看到一種更有效率的方法。本質(zhì)思想就是遇到之后,你走不通了那么這一段走不通的都丟給*來代替。

public class Solution {
    public boolean isMatch(String str, String pattern) {
        if(str==null||pattern==null)
            return false;
        int s=0;
        int p=0;
        int match=0;//*所代表的范圍的結(jié)尾
        int starIdx=-1;//*的位置
        while(s<str.length()){
            if(p<pattern.length()&&(pattern.charAt(p)=='?'||pattern.charAt(p)==str.charAt(s))){
                s++;
                p++;
            }
            else if(p<pattern.length()&&pattern.charAt(p)=='*'){
                starIdx=p;
                match=s;
                p++;//s不增加,因?yàn)橛锌赡?代表空
            }
            else if(starIdx!=-1){
                p=starIdx+1;
                match++;
                s=match;
            }else{
                return false;
            }
        }
        while(p<pattern.length()&&pattern.charAt(p)=='*'){//如果后邊都是*的話,可以當(dāng)做什么也沒有
            p++;
        }
        return p==pattern.length();
    }
}

題目四:

[leetcode97]https://leetcode.com/problems/interleaving-string/
給了三個(gè)字符串str1 str2 str3 str3是不是str1和str2的交叉

public class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        int len1=s1.length();
        int len2=s2.length();
        int len3=s3.length();
        if(len1+len2!=len3)
            return false;
        boolean[][] dp=new boolean[len1+1][len2+1];//dp[i+1][j+1]表示s1[0,i] s2[0,j]時(shí)候滿足條件交接到s3[0,i+j+1]
        dp[0][0]=true;
        for(int j=0;j<len2;j++){
            if(s2.charAt(j)==s3.charAt(j))
                dp[0][j+1]=true;
            else
                break;
        }
        for(int i=0;i<len1;i++){
            if(s1.charAt(i)==s3.charAt(i))
                dp[i+1][0]=true;
            else
                break;
        }
        char c1;
        char c2;
        char c3;
        for(int i=0;i<len1;i++){
            c1=s1.charAt(i);
            for(int j=0;j<len2;j++){
                c2=s2.charAt(j);
                c3=s3.charAt(i+j+1);
                if(c1==c3&&c2==c3)
                    dp[i+1][j+1]=dp[i][j+1]||dp[i+1][j];
                else if(c2==c3)
                    dp[i+1][j+1]=dp[i+1][j];
                else if(c1==c3)
                    dp[i+1][j+1]=dp[i][j+1];
            }
        }
        return dp[len1][len2];
    }
}

題目五

[leetcode115]https://leetcode.com/problems/distinct-subsequences/
一個(gè)字符串中包含多少個(gè)另一個(gè)字符串

public class Solution {
    public int numDistinct(String s, String t) {
        int len1=s.length();
        int len2=t.length();
        int[][] dp=new int[len1+1][len2+1];
        dp[0][0]=1;
        for(int i=0;i<len1;i++){
            dp[i+1][0]=1;
        }
        for(int i=0;i<len1;i++){
            for(int j=0;j<len2;j++){
                if(s.charAt(i)==t.charAt(j)){
                    dp[i+1][j+1]=dp[i][j]+dp[i][j+1];
                }
                else
                    dp[i+1][j+1]=dp[i][j+1];
            }
        }
        return dp[len1][len2];
    }
}
public class Solution {
    public int numDistinct(String s, String t) {
        int len1=s.length();
        int len2=t.length();
        int[] dp=new int[len2+1];
        int prev;
        int cur;
        for(int i=0;i<len1;i++){
            dp[0]=1;
            prev=1;
            char c=s.charAt(i);
            for(int j=0;j<len2;j++){
                if(t.charAt(j)==c){
                    cur=dp[j+1]+dp[j];
                }
                else
                    cur=dp[j+1];
                dp[j]=prev;
                prev=cur;
            }
            dp[len2]=prev;
        }
        return dp[len2];
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評(píng)論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,786評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,796評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,995評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,230評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,697評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容

  • 動(dòng)態(tài)規(guī)劃(Dynamic Programming) 本文包括: 動(dòng)態(tài)規(guī)劃定義 狀態(tài)轉(zhuǎn)移方程 動(dòng)態(tài)規(guī)劃算法步驟 最長(zhǎng)...
    廖少少閱讀 3,316評(píng)論 0 18
  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗(yàn)。 張土汪:刷leetcod...
    土汪閱讀 12,762評(píng)論 0 33
  • 引言 字符串匹配一直是計(jì)算機(jī)科學(xué)領(lǐng)域研究和應(yīng)用的熱門領(lǐng)域,算法的改進(jìn)研究一直是一個(gè)十分困難的課題。作為字符串匹配中...
    潮汐行者閱讀 1,669評(píng)論 2 6
  • 基礎(chǔ)概念 字符串:S[0..n],S是一個(gè)字符串,長(zhǎng)度為n。S本質(zhì)上是一個(gè)字符數(shù)組,數(shù)組的每個(gè)元素都是一個(gè)字符; ...
    RobotBerry閱讀 1,163評(píng)論 0 3
  • 分治方法 將問題劃分成互不相交的子問題 遞歸地求解子問題 將子問題的解組合起來 動(dòng)態(tài)規(guī)劃(兩個(gè)要素:最優(yōu)子結(jié)構(gòu)、子...
    superlj666閱讀 509評(píng)論 0 0