2020年6月1日 星期一

Leetcode題解 Python & C#:五月挑戰DAY31 Edit Distance

給兩個字串,要找最小的編輯次數讓 word1 變成 word2。編輯只有三種操作:insert、delete、replace。

這是hard題,蠻有意思的,可以找出像是基因序列相似的程度,或是字典中找相似的詞。

如果用人的概念去想,要怎麼選,要用哪種換法,基準點在哪裡,似乎不是那麼好想像的。
但是能確定朝著每個操作,是 word1 和 word2 之間的交換。

舉例:word1 = ABC, word2 = BABC。
可以想到把 word2[0]做delete,或 word1 insert(0,"B"),就可以得到 word1 == word2。
為了好講,這裡不探討 word2 上做操作,統一以 word1 當基準,也就是只看 word1 -> word2。

像是 word1[0] == word2[1] == "A",在這位置上不用操作,因為二者相等。說到「位置」這裡換個思考 DP[p1][p2] ,
二者的方向關係是一個表格DP,若 word1[p1] == word2[p2] , 則 dp[p1][2] 的基本值等於0,即不需要任何操作,反之則 1。
  
題目要找出最小操作次數,也可以想像把操作數往右下角,一路取最小壘加,最後的結果就是最小操作次數。

如果 word1[p1] != word2[p2],代表要進行操作,在 DP[p1][p2] 取 {DP[p1-1][p2-1], DP[p1-1][p2], DP[p1][p2-1]}三者最小值加 1 (基本值)。
分別對應{replace, delete, insert}三種操作中取最佳方案到DP[p1][p2] ,看 DP[p1][p2] 的「相對位置」來決定是哪種操作,本身基本值 1 有三種操作的可能,至少一定是其中一個。

 BABC
A11
B1X

在位置[0][1],[0][0] 是 [p1][p2-1],即[0][0] insert(0, Char) (B)
在位置[1][0],[0][0] 是 [p1-1][p2],即[0][0] del word1[0] (A)
在X位置[1][1],對X位置來[0][0]位罝是 replace (A -> B) ,[0][1]位置是 del word1[0],而 [1][0] insert(1, Char)。

那 word1[p1] == word2[p2] 時呢? DP[p1][p2] = ?
我們取右下角代表經過操作次數使從頭到p1,p2位置都對上,最右下角即從頭到尾都對上的操作次數。
一旦 word1[p1] == word2[p2],所需要的次數,即前面都對上所需要的次數,DP[p1][p2] = DP[p1-1][p2-1]。(左上那個 相當於子問題(二字串編輯)的解)

如果在 row = 0 或 col = 0 時 word1[p1] == word2[p2] 相等, DP[p1-1][p2-1] 會超出範圍。那該等於多少?
要是相等,那麼要加上使前面相等的次數。如果是[0][0],那麼就是0 次, 如果是 [1][0] 或 [0][1],那麼前面要一樣,不是 delete 就是 insert 前面的,為 1 次。
同理[p1][0]、[0][p2],邊際無法做 replace,建議獨立出來處理掉。

像這種有邊際問題的,可以增加表格範圍(邊際欄列),又或者先拿出來處理。如果把邊際的條件寫在一起,不僅不方便讀,也拖累效率。

Python(邊際欄列)
class Solution:
    def minDistance(self, word1: str, word2: str) -> int:  
	from functools import lru_cache      
        rows, cols = len(word1)+1, len(word2)+1        
        @lru_cache(None)
        def dfs(row, col):
            if row == 0 or col == 0:
                return max(row, col)
            other = set()     
            if row > 0 or col > 0:
                if word1[row-1] == word2[col-1]:
                    ans = dfs(row-1, col-1)
                else:
                    if row > 0 and col > 0:               
                        other.add(dfs(row-1, col-1))
                    if row > 0:
                        other.add(dfs(row-1, col))
                    if col > 0:
                        other.add(dfs(row, col-1))
                    ans = min(other)+1
            return int(ans)

        return dfs(rows-1, cols-1)
C#
public class Solution {
    public int MinDistance(string word1, string word2) {
        var memo = new int[word1.Length+1, word2.Length+1];
        for(int i = 0; i < word1.Length+1; i++)
            memo[i, 0] = i; 
        for(int j = 0; j < word2.Length+1; j++)
            memo[0, j] = j;        
        for(int i = 1; i < word1.Length + 1; i++)
        {
            for(int j = 1; j < word2.Length + 1; j++)
            {
                if(word1[i-1] == word2[j-1])
                    memo[i, j] = memo[i-1, j-1];
                else
                {
                    memo[i, j] = Math.Min(memo[i-1, j], memo[i, j-1]);
                    memo[i, j] = Math.Min(memo[i, j], memo[i-1, j-1]) + 1;                     
                }
            }
        }
        return memo[word1.Length, word2.Length];
    }
}