1. 程式人生 > 其它 >LeetCode:Regular Expression Matching

LeetCode:Regular Expression Matching

LeetCode:Regular Expression Matching

題目源自於10. Regular Expression Matching

題目描述

給定輸入字串s,和模式字串p

  • s只包含小寫字母,assert( 0 <= len(s) <= 20)
  • p只包含小寫字母,特殊字元.*
    • .:匹配任意一個字元。
    • * :匹配[0, ~]個前置字元。
    • assert( 0 <= len(p) <= 30)
  • 輸入保證,```*``前面必定有有效的字元。

解題思路

通用的字串匹配演算法可以採用回朔的方式,採用模式棧+匹配成功字元棧進行匹配:

  1. 當模式棧內容等於p,匹配成功棧內容等於s
    時,匹配成功。
  2. s匹配成功,p未完結,需要跳過字元*的無效對。
  3. 當匹配字元是普通字元和.時,匹配棧和匹配棧按照匹配結果按序壓棧即可。
  4. 當下一個匹配字元為*,會產生下列分流:
    • *和當前匹配字元入匹配棧。
    • 待匹配字元和上一個匹配字元相等,待匹配字元入匹配成功字元棧,繼續匹配當前字元。

最終可得到如下演算法:

class Solution:
    def isMatch(self, s, p):
        return self._is_match(s, 0, p, 0)
            
    def _is_match(self, s, s_index, p, p_index):
        if s_index == len(s):
            if p_index == (len(p) - 1) and p[p_index] == "*": # 最後一個是*,跳過
                p_index = p_index + 1
            while (p_index + 1) < len(p): # 跳過末尾的 "字元*"對
                if p[p_index+1] != "*":
                    break
                p_index = p_index + 2
            if p_index == len(p):
                return True
            
        if s_index == len(s) or p_index == len(p):
            return False
        is_matched = (p[p_index] == "." or p[p_index] == s[s_index])
        if is_matched: # 匹配一個字元
            if self._is_match(s, s_index+1, p, p_index+1):
                return True
        if (p_index + 1) < len(p) and p[p_index+1] == "*":
            if is_matched and self._is_match(s, s_index+1, p, p_index): # 再使用前一個字元
                return True
            if self._is_match(s, s_index, p, p_index+2): # 跳過前一個字元
                return True
        return False

回朔的方法的時間複雜度較高,除此解法外,本題還適用於動態規劃

動態規劃問題需要滿足:

  1. 問題具有最優子結構性質:如果問題的最優解包含的子問題的解也是最優的,我們就稱該問題具有最優子結構。
  2. 無後效性:當前的若干個狀態值一旦確定,則此後過程的演變就只和這若干個狀態的值有關,和之前是採取哪種手段或經過哪條路徑演變到當前的這個若干個狀態,沒有關係。

動態規劃的常見解題步驟:

  1. 將問題分解成子問題。
  2. 確定狀態。
  3. 確定初始狀態。
  4. 確定狀態轉移方程。

下面以上述方法對題目進行解構:

假設:

dp = [[False for _ in range (len(s) + 1)] for _ in range(len(p) + 1)]

# y 為 當前s中待匹配字元的index + 1
# x 為 當前p中匹配字元的index + 1
  1. 本題的子問題在於p中當前匹配字元和s中待匹配字元是否匹配。
  2. 狀態為當前匹配字元和待匹配字元:
    1. 上一個字元的匹配情況。
    2. 上兩個字元的匹配情況。
    3. 初始狀態的匹配情況。
  3. 狀態空間為所有字元的匹配情況。
  4. 通過下圖分析初始狀態:
    • dp[0][0] = True
    • True向所有字元*組合擴散。
  5. 通過下圖確定轉移方程:
    • 字元為非*dp[x][y]= (dp[x-1][y-1] and s[y-1] == p[x-1] or p[x-1] == ".")
    • 當字元為*
      1. dp[x][y] |= dp[x-2][y]
      2. dp[x][y] |= (dp[x-1][y-1] and s[x-1] == p[y-1] or p[y-1] == "."))
狀態轉移圖

最終得到演算法:

class Solution:
    def isMatch(self, s, p):
        dp = [[False for _ in range (len(s) + 1)] for _ in range(len(p) + 1)]
        
        dp[0][0] = True
        for i in range(1, len(p)+1):
            if i >= 2:
                dp[i][0] = dp[i-2][0] and p[i-1] == "*"
                
        for i in range(1, len(p)+1):
            for j in range(1, len(s)+1):
                if p[i-1] == "*":
                    dp[i][j] = (dp[i][j-1] and p[i-2] in (s[j-1], ".")) or dp[i-2][j]
                else:
                    dp[i][j] = dp[i-1][j-1] and p[i-1] in (s[j-1], ".")
                    
        return dp[len(p)][len(s)]