代碼隨想錄算法訓練營第45天 | 第九章 動態(tài)規(guī)劃part12:115.不同的子序列塞帐、583. 兩個字符串的刪除操作拦赠、72. 編輯距離、編輯距離總結(jié)篇

第九章 動態(tài)規(guī)劃part12

115.不同的子序列

但相對于剛講過 392.判斷子序列葵姥,本題 就有難度了 荷鼠,感受一下本題和 392.判斷子序列 的區(qū)別。
文章講解

思路

  1. 確定dp數(shù)組(dp table)以及下標的含義
    dp[i][j]:以i-1為結(jié)尾的s子序列中出現(xiàn)以j-1為結(jié)尾的t的個數(shù)為dp[i][j]榔幸。

  2. 確定遞推公式
    這一類問題允乐,基本是要分析兩種情況

  • s[i - 1] 與 t[j - 1]相等
  • s[i - 1] 與 t[j - 1] 不相等
  • 當s[i - 1] 與 t[j - 1]相等時矮嫉,dp[i][j]可以有兩部分組成。
    一部分是用s[i - 1]來匹配牍疏,那么個數(shù)為dp[i - 1][j - 1]蠢笋。即不需要考慮當前s子串和t子串的最后一位字母,所以只需要 dp[i-1][j-1]鳞陨。
    一部分是不用s[i - 1]來匹配昨寞,個數(shù)為dp[i - 1][j]。
  • 為什么還要考慮 不用s[i - 1]來匹配厦滤,都相同了指定要匹配啊援岩。
    例如: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的掏导,但是字符串s也可以不用s[3]來匹配享怀,即用s[0]s[1]s[2]組成的bag。
    當然也可以用s[3]來匹配碘菜,即:s[0]s[1]s[3]組成的bag凹蜈。
  • 當s[i - 1] 與 t[j - 1]不相等時,dp[i][j]只有一部分組成忍啸,不用s[i - 1]來匹配(就是模擬在s中刪除這個元素)仰坦,即:dp[i - 1][j]
    所以遞推公式為:dp[i][j] = dp[i - 1][j];
  1. dp數(shù)組如何初始化
    從遞推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][j] 是從上方和左上方推導而來,如圖:计雌,那么 dp[i][0] 和dp[0][j]是一定要初始化的悄晃。
    每次當初始化的時候,都要回顧一下dp[i][j]的定義凿滤,不要憑感覺初始化妈橄。
  • dp[i][0]表示什么呢?
    dp[i][0] 表示:以i-1為結(jié)尾的s可以隨便刪除元素翁脆,出現(xiàn)空字符串的個數(shù)眷蚓。
    那么dp[i][0]一定都是1,因為也就是把以i-1為結(jié)尾的s反番,刪除所有元素沙热,出現(xiàn)空字符串的個數(shù)就是1。
  • 再來看dp[0][j]罢缸,dp[0][j]:空字符串s可以隨便刪除元素篙贸,出現(xiàn)以j-1為結(jié)尾的字符串t的個數(shù)。
    那么dp[0][j]一定都是0枫疆,s如論如何也變成不了t爵川。
  • 最后就要看一個特殊位置了,即:dp[0][0] 應該是多少息楔。
    dp[0][0]應該是1寝贡,空字符串s扒披,可以刪除0個元素,變成空字符串t兔甘。
  1. 確定遍歷順序
    從遞推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][j]都是根據(jù)左上方和正上方推出來的谎碍。

  2. 舉例推導dp數(shù)組
    以s:"baegg",t:"bag"為例洞焙,推導dp數(shù)組狀態(tài)如下:


    image.png
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];
        for(int i = 0; i < len1; i++) dp[i][0] = 1;
        for(int j = 1; j < len2; j++) dp[0][j] = 0;
        for(int i = 1; i <= len1; i++){
            for(int j = 1; j <= len2; j++){
                if(s.charAt(i - 1) == t.charAt(j - 1)){
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                }else{
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[len1][len2];
    }
}

583. 兩個字符串的刪除操作

本題和動態(tài)規(guī)劃:115.不同的子序列 相比,其實就是兩個字符串都可以刪除了拯啦,情況雖說復雜一些澡匪,但整體思路是不變的。
文章講解

思路

  1. 確定dp數(shù)組(dp table)以及下標的含義
    dp[i][j]:以i-1為結(jié)尾的字符串word1褒链,和以j-1位結(jié)尾的字符串word2唁情,想要達到相等,所需要刪除元素的最少次數(shù)甫匹。

  2. 確定遞推公式

  • 當word1[i - 1] 與 word2[j - 1]相同的時候
  • 當word1[i - 1] 與 word2[j - 1]不相同的時候
  • 當word1[i - 1] 與 word2[j - 1]相同的時候甸鸟,dp[i][j] = dp[i - 1][j - 1];
  • 當word1[i - 1] 與 word2[j - 1]不相同的時候,有三種情況:
    • 情況一:刪word1[i - 1]兵迅,最少操作次數(shù)為dp[i - 1][j] + 1
    • 情況二:刪word2[j - 1]抢韭,最少操作次數(shù)為dp[i][j - 1] + 1
    • 情況三:同時刪word1[i - 1]和word2[j - 1],操作的最少次數(shù)為dp[i - 1][j - 1] + 2
      那最后當然是取最小值恍箭,所以當word1[i - 1] 與 word2[j - 1]不相同的時候刻恭,遞推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
      因為 dp[i][j - 1] + 1 = dp[i - 1][j - 1] + 2,所以遞推公式可簡化為:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
      從字面上理解 就是 當 同時刪word1[i - 1]和word2[j - 1]扯夭,dp[i][j-1] 本來就不考慮 word2[j - 1]了鳍贾,那么我在刪 word1[i - 1]胸遇,是不是就達到兩個元素都刪除的效果地啰,即 dp[i][j-1] + 1。
  1. dp數(shù)組如何初始化
    從遞推公式中哼蛆,可以看出來构拳,dp[i][0] 和 dp[0][j]是一定要初始化的咆爽。
    dp[i][0]:word2為空字符串,以i-1為結(jié)尾的字符串word1要刪除多少個元素隐圾,才能和word2相同呢伍掀,很明顯dp[i][0] = i。
    dp[0][j]的話同理暇藏。

  2. 確定遍歷順序
    從遞推公式 dp[i][j] = min(dp[i - 1][j - 1] + 2, min(dp[i - 1][j], dp[i][j - 1]) + 1); 和dp[i][j] = dp[i - 1][j - 1]可以看出dp[i][j]都是根據(jù)左上方蜜笤、正上方、正左方推出來的盐碱。
    所以遍歷的時候一定是從上到下把兔,從左到右沪伙,這樣保證dp[i][j]可以根據(jù)之前計算出來的數(shù)值進行計算。

  3. 舉例推導dp數(shù)組

    以word1:"sea"县好,word2:"eat"為例围橡,推導dp數(shù)組狀態(tài)圖如下:
    image.png
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];
        for(int i = 0; i <= len1; i++) dp[i][0] = i;
        for(int j = 0; j <= len2; j++) dp[0][j] = j;
        for(int i = 1; i <= len1; i++){
            for(int j = 1; j <= len2; j++){
                if(word1.charAt(i - 1) == word2.charAt(j - 1)){
                    dp[i][j] = dp[i - 1][j - 1]; //注意這里
                }else{
                    dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);               
                }
            }
        }
        return dp[len1][len2];
    }
}

動態(tài)規(guī)劃二

本題和 動態(tài)規(guī)劃:1143.最長公共子序列 基本相同,只要求出兩個字符串的最長公共子序列長度即可缕贡,那么除了最長公共子序列之外的字符都是必須刪除的翁授,最后用兩個字符串的總長度減去兩個最長公共子序列的長度就是刪除的最少步數(shù)。

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];
        char[] char1 = word1.toCharArray();
        char[] char2 = word2.toCharArray();

        for(int i = 1; i <= len1; i++){
            for(int j = 1; j <= len2; j++){
                if(char1[i - 1] == char2[j - 1]){
                    dp[i][j] = dp[i - 1][j - 1] + 1; 
                }else{
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);               
                }
            }
        }
        return len1 + len2 - (2 * dp[len1][len2]);
    }
}

72. 編輯距離

最終我們迎來了編輯距離這道題目晾咪,之前安排題目都是為了 編輯距離做鋪墊收擦。
文章講解

思路

  1. 確定dp數(shù)組(dp table)以及下標的含義
    dp[i][j] 表示以下標i-1為結(jié)尾的字符串word1,和以下標j-1為結(jié)尾的字符串word2谍倦,最近編輯距離為dp[i][j]塞赂。

  2. 確定遞推公式
    在確定遞推公式的時候,首先要考慮清楚編輯的幾種操作昼蛀,整理如下4種情況:

if (word1[i - 1] == word2[j - 1])
    不操作
if (word1[i - 1] != word2[j - 1])
    增
    刪
    換
  • if (word1[i - 1] == word2[j - 1]) 那么說明不用任何編輯宴猾,dp[i][j] 就應該是 dp[i - 1][j - 1],即dp[i][j] = dp[i - 1][j - 1];

  • if (word1[i - 1] != word2[j - 1])叼旋,此時就需要編輯了仇哆,如何編輯呢?

  • 操作一:word1刪除一個元素送淆,那么就是以下標i - 2為結(jié)尾的word1 與 j-1為結(jié)尾的word2的最近編輯距離 再加上一個操作税产。
    即 dp[i][j] = dp[i - 1][j] + 1;

  • 操作二:word2刪除一個元素,那么就是以下標i - 1為結(jié)尾的word1 與 j-2為結(jié)尾的word2的最近編輯距離 再加上一個操作偷崩。
    即 dp[i][j] = dp[i][j - 1] + 1;

  • 怎么都是刪除元素辟拷,添加元素去哪了:
    word2添加一個元素,相當于word1刪除一個元素阐斜,例如 word1 = "ad" 衫冻,word2 = "a",word1刪除元素'd' 和 word2添加一個元素'd'谒出,變成word1="a", word2="ad"隅俘, 最終的操作數(shù)是一樣的。

  • 操作三:替換元素笤喳,word1替換word1[i - 1]为居,使其與word2[j - 1]相同,此時不用增刪加元素杀狡。
    可以回顧一下蒙畴,if (word1[i - 1] == word2[j - 1])的時候我們的操作 是 dp[i][j] = dp[i - 1][j - 1]
    那么只需要一次替換的操作,就可以讓 word1[i - 1] 和 word2[j - 1] 相同。
    所以 dp[i][j] = dp[i - 1][j - 1] + 1;

  • 綜上膳凝,當 if (word1[i - 1] != word2[j - 1]) 時取最小的碑隆,即:dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;

  1. dp數(shù)組如何初始化
  • 再回顧一下dp[i][j]的定義:
    dp[i][j] 表示以下標i-1為結(jié)尾的字符串word1,和以下標j-1為結(jié)尾的字符串word2蹬音,最近編輯距離為dp[i][j]上煤。
  • 那么dp[i][0] 和 dp[0][j] 表示什么呢?
    dp[i][0] :以下標i-1為結(jié)尾的字符串word1著淆,和空字符串word2劫狠,最近編輯距離為dp[i][0]。
  • 那么dp[i][0]就應該是i永部,對word1里的元素全部做刪除操作嘉熊,即:dp[i][0] = i;
  • 同理dp[0][j] = j;
  1. 確定遍歷順序
    從如下四個遞推公式:
    dp[i][j] = dp[i - 1][j - 1]
    dp[i][j] = dp[i - 1][j - 1] + 1
    dp[i][j] = dp[i][j - 1] + 1
    dp[i][j] = dp[i - 1][j] + 1

    可以看出dp[i][j]是依賴左方,上方和左上方元素的扬舒,如圖:
    image.png
  2. 舉例推導dp數(shù)組
    以示例1為例,輸入:word1 = "horse", word2 = "ros"為例凫佛,dp矩陣狀態(tài)圖如下:


    image.png
class Solution {
    public int minDistance(String word1, String word2) {
        int len1 = word1.length();
        int len2 = word2.length();
        char[] char1 = word1.toCharArray();
        char[] char2 = word2.toCharArray();
        int[][] dp = new int[len1 + 1][len2 + 1];
        for(int i = 0; i <= len1; i++) dp[i][0] = i;
        for(int j = 0; j <= len2; j++) dp[0][j] = j;
        for(int i = 1; i <= len1; i++){
            for(int j = 1; j <= len2; j++){
                if(char1[i - 1] == char2[j - 1]){
                    dp[i][j] = dp[i - 1][j - 1];
                }else{
                    dp[i][j] = Math.min(Math.min(dp[i-1][j], dp[i][j - 1]), dp[i-1][j-1]) + 1;
                }
            }
        }
        return dp[len1][len2];
    }
}

編輯距離總結(jié)篇

做一個總結(jié)吧

https://programmercarl.com/%E4%B8%BA%E4%BA%86%E7%BB%9D%E6%9D%80%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%8C%E5%8D%A1%E5%B0%94%E5%81%9A%E4%BA%86%E4%B8%89%E6%AD%A5%E9%93%BA%E5%9E%AB.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末讲坎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子愧薛,更是在濱河造成了極大的恐慌晨炕,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毫炉,死亡現(xiàn)場離奇詭異瓮栗,居然都是意外死亡,警方通過查閱死者的電腦和手機瞄勾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門费奸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人进陡,你說我怎么就攤上這事愿阐。” “怎么了趾疚?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵缨历,是天一觀的道長。 經(jīng)常有香客問我糙麦,道長辛孵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任赡磅,我火速辦了婚禮魄缚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仆邓。我一直安慰自己鲜滩,他們只是感情好伴鳖,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著徙硅,像睡著了一般榜聂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嗓蘑,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天须肆,我揣著相機與錄音,去河邊找鬼桩皿。 笑死豌汇,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的泄隔。 我是一名探鬼主播拒贱,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼佛嬉!你這毒婦竟也來了逻澳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤暖呕,失蹤者是張志新(化名)和其女友劉穎斜做,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體湾揽,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡瓤逼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了库物。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霸旗。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖艳狐,靈堂內(nèi)的尸體忽然破棺而出定硝,到底是詐尸還是另有隱情,我是刑警寧澤毫目,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布蔬啡,位于F島的核電站,受9級特大地震影響镀虐,放射性物質(zhì)發(fā)生泄漏箱蟆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一刮便、第九天 我趴在偏房一處隱蔽的房頂上張望空猜。 院中可真熱鬧,春花似錦、人聲如沸辈毯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谆沃。三九已至钝凶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唁影,已是汗流浹背耕陷。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留据沈,地道東北人哟沫。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像锌介,于是被迫代替她去往敵國和親嗜诀。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

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