java動態規劃之編輯距離

想了解更多數據結構以及算法題,可以關注微信公衆號“數據結構和算法”,每天一題爲你精彩解答。也可以掃描下面的二維碼關注
在這裏插入圖片描述

給你兩個單詞 word1 和 word2,請你計算出將 word1 轉換成 word2 所使用的最少操作數 。

你可以對一個單詞進行如下三種操作:

插入一個字符

刪除一個字符

替換一個字符

示例 1:

輸入:word1 = “horse”, word2 = “ros”

輸出:3

解釋:

horse -> rorse (將 ‘h’ 替換爲 ‘r’)

rorse -> rose (刪除 ‘r’)

rose -> ros (刪除 ‘e’)

總有共3步

示例 2:

輸入:word1 = “intention”, word2 = “execution”

輸出:5

解釋:

intention -> inention (刪除 ‘t’)

inention -> enention (將 ‘i’ 替換爲 ‘e’)

enention -> exention (將 ‘n’ 替換爲 ‘x’)

exention -> exection (將 ‘n’ 替換爲 ‘c’)

exection -> execution (插入 ‘u’)

總有共5步

問題分析:

1,如果想把word1變爲word2,對於word1的操作我們有3種方式:
刪除一個字符
添加一個字符
修改一個字符
這就好比對數據庫的增刪改查一樣,不過這裏沒有查找。

我們用dp[i][j]表示把word1的前i個字符變爲word2的前j個字符所需要的最少編輯距離,這裏要分兩種情況

1,當word1[i]==word2[j]:也就是說word1的第i個字符和word2的第j個字符相等,我們不需要修改word1的第i個字符,所以這時dp[i][j]=dp[i-1][j-1]。

2,當word1[i]!=word2[j]:也就是說word1的第i個字符和word2的第j個字符不相等。這時我們可以有3種操作來計算dp[i][j];

,dp[i-1][j]:表示的是word1的前i-1個字符和word2的前j個字符的最小編輯距離,在dp[i][j]中我們只需要把word1中第i個字符刪除就是dp[i-1][j],所以dp[i][j]=dp[i-1][j]+1。

,dp[i][j-1]:表示的是word1的前i個字符和word2的前j-1個字符的最小編輯距離,在dp[i][j]中我們只需要把word2中的第j個字符刪除就是dp[i][j-1],所以dp[i][j]=dp[i][j-1]+1。(注:我們這一步明明是增,但這裏爲什麼是刪,因爲我們這裏刪的是word2的字符,增和刪是相對的,word2字符的刪除也可以認爲是word1字符的添加,舉個例子,比如word1=“a”,word2=“ab”,我們在word1中添加一個b或者在word2中刪除一個b,最短編輯距離都是一樣的)

,dp[i-1][j-1]:表示的是word1的前i-1個字符和word2的前j-1個字符的最小編輯距離,我們只需要把word1的第i個字符修改爲word2的第j個字符就可以求出dp[i][j],所以dp[i][j]=dp[i-1][j-1]+1。
上面三種情況我們要選最小的,所以遞推公式

1,當word1[i]==word2[j]: dp[i][j]=dp[i-1][j-1]
2,當word1[i]!=word2[j]:

dp[i][j]=min{dp[i-1][j-1],dp[i-1][j],dp[i][j-1]}+1 邊界條件:
如果word1爲空,我們要把word1變爲word2就是不停的插入, 如果word2爲空,我們要把word1變爲word2就是不停的刪除。

下面我們來畫個圖看一下
在這裏插入圖片描述

舉個例子,

比如(0,0)格內,我們只需要把h變爲r即可,所以需要1步。

比如(0,1)格內,我們只需要把h變爲r,然後刪除O,所以需要2步。

比如(1,0)格內,我們只需要把h變爲r,然後在添加一個O,所以需要2步。

比如(1,1)格內,因爲O==O,我們只需要把h變爲r即可,所以需要1步。

看懂了上面的分析過程,代碼就容易多了,我們來看下代碼

 public static int minDistance(String word1, String word2) {
     int length1 = word1.length();
     int length2 = word2.length();
     if (length1 * length2 == 0)
         return length1 + length2;//如果有一個爲空,直接返回另一個的長度即可
     int dp[][] = new int[length1 + 1][length2 + 1];
     for (int i = 0; i <= length1; i++) {
         dp[i][0] = i;//邊界條件,相當於word1的刪除操作
     }
    for (int i = 0; i <= length2; i++) {
        dp[0][i] = i;//邊界條件,相當於word1的添加操作
    }
    for (int i = 1; i <= word1.length(); i++) {
        for (int j = 1; j <= length2; j++) {//下面是上面分析的遞推公式
            if (word1.charAt(i - 1) == word2.charAt(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;
            }
        }
    }
    Util.printTwoIntArrays(dp);//測試數據的打印,可去掉
    return dp[length1][length2];
}

代碼比較簡單,核心代碼也就15到19行,其他的也就是一些邊界的判斷。

我們還用上面的數據測試一下,看一下打印結果

public static void main(String args[]) {
    System.out.println(minDistance("horse", "ros"));
}

結果如下
在這裏插入圖片描述
和我們上面分析的完全一致。

代碼優化

我們看到雖然dp是二維數組,但我們計算的時候每個元素只和他的左邊,上邊,左上角的3個值有關,所以這裏我們還可以優化一下,使用一維數組,我們看下代碼

 public static int minDistance2(String word1, String word2) {
     int length1 = word1.length();
     int length2 = word2.length();
     if (length1 * length2 == 0)
         return length1 + length2;
     int dp[] = new int[length2 + 1];
     for (int i = 1; i <= length2; i++) {
         dp[i] = i;
     }
    int last = 0;
    for (int i = 1; i <= word1.length(); i++) {
        last = dp[0];
        dp[0] = i;
        for (int j = 1; j <= length2; j++) {
            int temp = dp[j];
            if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                dp[j] = last;
            } else {
                dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), last) + 1;
            }
            last = temp;
        }
        Util.printIntArrays(dp);//這兩行代碼僅做測試打印數據使用,可刪除
        System.out.println();
    }
    return dp[length2];
}

代碼中last記錄的是左上角的值,因爲這個值會被覆蓋,所以我們提前記錄了下來,我們還用上面的代碼測試一下,再來看一下打印結果

結果和我們上面分析的完全一致。

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