1. 程式人生 > >Leetcode 72:編輯距離(超詳細的解法!!!)

Leetcode 72:編輯距離(超詳細的解法!!!)

給定兩個單詞 word1word2,計算出將 word1 轉換成 word2 所使用的最少運算元 。

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

  1. 插入一個字元
  2. 刪除一個字元
  3. 替換一個字元

示例 1:

輸入: word1 = "horse", word2 = "ros"
輸出: 3
解釋: 
horse -> rorse (將 'h' 替換為 'r')
rorse -> rose (刪除 'r')
rose -> ros (刪除 'e')

示例 2:

輸入: word1 = "intention", word2 = "execution"
輸出: 5
解釋: 
intention -> inention (刪除 't')
inention -> enention (將 'i' 替換為 'e')
enention -> exention (將 'n' 替換為 'x')
exention -> exection (將 'n' 替換為 'c')
exection -> execution (插入 'u')

解題思路

這個問題非常簡單,有點類似於LCS問題,所以我們很容易想到通過動態規劃來求解這個問題。我們首先思考一下遞迴過程。我們通過兩個指標ij分別指向word1word2。我們定義函式 f ( i , j )

f(i,j) 表示word1[:i]轉換為word2[:j]需要的最少步驟。

word1: h o r s e
         i
word2: r o s
         j

我們首先要比較word1[i-1]word2[j-1]是不是相同,如果相同的話,我們就不用做任何操作,所以此時 f ( i

, j ) = f ( i 1 , j 1 ) f(i,j)=f(i-1,j-1) ij都向前挪一個位置)。

接著對於不相同的時候我們的情況比較複雜,我們有三種處理手段,分別是insertreplaceremove。我們先看insert操作。我們insert完之後,也就是word1會中的元素會保持不變,而j會向前挪一個位置,也就是 f ( i , j ) = f ( i , j 1 ) + 1 f(i,j)=f(i,j-1)+1 。接著考慮replace操作,replace會減少word1word2中一個需要比較的元素(ij會向前挪一個位置),也就是 f ( i , j ) = f ( i 1 , j 1 ) + 1 f(i,j)=f(i-1,j-1)+1 。我們接著考慮最後一個remove操作,這個就很容易了,word1中會減少一個需要比較的元素,而我們j的位置不變,也就是 f ( i , j ) = f ( i 1 , j ) + 1 f(i,j)=f(i-1,j)+1 。所以我們最後的結果相當三者取最小值即可。

  • f ( i , j ) = m i n ( f ( i 1 , j ) , f ( i , j 1 ) , f ( i 1 , j 1 ) ) + 1    i f   w o r d 1 [ i ] w o r d 2 [ j ] f(i,j)=min(f(i-1,j),f(i,j-1),f(i-1,j-1))+1\ \ if\ word1[i]\neq word2[j]
  • f ( i , j ) = f ( i 1 , j 1 )     i f   w o r d 1 [ i ] = w o r d 2 [ j ] f(i,j)=f(i-1,j-1)\ \ \ if \ word1[i]=word2[j]

接著我們要考慮初始條件,也就是word1word2為空的情況,此時也非常簡單 f ( i , 0 ) = i f(i,0)=i f ( 0 , j ) = j f(0,j)=j 。所以我們最後的程式碼也就很容易了

class Solution:
    def minDistance(self, word1, word2):
        """
        :type word1: str
        :type word2: str
        :rtype: int
        """
        word1_len, word2_len = len(word1), len(word2)
        mem = [[0]*(word2_len+1) for _ in range(word1_len+1)]
        for i in range(1, word1_len+1):
            mem[i][0] = i
        for j in range(1, word2_len+1):
            mem[0][j] = j

        for i in range(1, word1_len+1):
            for j in range(1, word2_len+1):
                mem[i][j] = min(mem[i-1][j-1]+(word1[i-1]!=word2[j-1]), mem[i][j-1]+1, mem[i-1][j]+1)

        return mem[-1][-1]

這個問題可以使用滾動陣列將空間複雜度優化為O(n)級別,但其實陣列開闢空間和賦值操作也就同樣的增加了時間損耗。

由於問題是求最少運算元,所以我們很容易想到通過廣度優先遍歷來解。只是這裡的廣度優先遍歷和以往的有些區別,我們每次需要訪問的邊有四種。也就是前文說的word1[i-1]==word2[j-1]的一種情況和word1[i-1]!=word2[j-1]的三種情況。實際的操作過程非常容易,程式碼如下

from heapq import heappush, heappop
class Solution:
    def minDistance(self, word1, word2):
        """
        :type word1: str
        :type word2: str
        :rtype: int
        """
        heap = [(0, word1, word2)]
        visited = set()
        while heap:
            d, w1, w2 = heappop(heap)
            if (w1, w2) in visited:
                continue
            visited.add((w1, w2))    
            if w1 == w2:
                return d
            if w1 and w2 and w1[0] == w2[0]:
                heappush(heap, (d, w1[1:], w2[1:]))
            else:
                if w1: 
                    heappush(heap, (d+1, w1[1:], w2)) #delete
                if w1 and w2: 
                    heappush(heap, (d+1, w1[1:], w2[1:])) #replace
                if w2: 
                    heappush(heap, (d+1, w1, w2[1:])) #add

實際的程式碼測試中,使用這種方式實現的程式碼要比前面使用動態規劃完成的速度快。

我將該問題的其他語言版本新增到了我的GitHub Leetcode

如有問題,希望大家指出!!!