想了解更多數據結構以及算法題,可以關注微信公衆號“數據結構和算法”,每天一題爲你精彩解答。也可以掃描下面的二維碼關注
給你兩個單詞 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記錄的是左上角的值,因爲這個值會被覆蓋,所以我們提前記錄了下來,我們還用上面的代碼測試一下,再來看一下打印結果
結果和我們上面分析的完全一致。