1. 程式人生 > >Leetcode演算法——44、正則式匹配

Leetcode演算法——44、正則式匹配

給定一個輸入字串 s 和一個模式字串 p,實現正則匹配,支援’?‘和’*’。

規則:

  • ‘?’ 可以匹配任何單個字元
  • ‘*’ 可以匹配任何字元的序列(包括空序列)
  • 需要匹配整個輸入字串 s,不能部分匹配。

備註:

  • 字串 s 只會包含小寫 a-z,可以為空。
  • 字串 p 只會包含小寫 a-z、? 或 *,可以為空。

Example 1:
Input:
s = “aa”
p = “a”
Output: false
Explanation: “a” does not match the entire string “aa”.

Example 2:
Input:
s = “aa”
p = ""
Output: true
Explanation: '

’ matches any sequence.

Example 3:
Input:
s = “cb”
p = “?a”
Output: false
Explanation: ‘?’ matches ‘c’, but the second letter is ‘a’, which does not match ‘b’.

Example 4:
Input:
s = “adceb”
p = “ab”
Output: true
Explanation: The first ‘’ matches the empty sequence, while the second '’ matches the substring “dce”.

Example 5:
Input:
s = “acdcb”
p = “a*c?b”
Output: false

思路

這道題與Leetcode演算法——10、實現正則匹配很相似,不同之處在於匹配規則不同。

1、遞迴法

從左到右依次掃描 s 和 p 的每個字元,看是否可以匹配。

  • 如果 s 為空,則只有 p 全部為 *,才能匹配上。
  • 否則:
    • 如果首字元相同,或者 p 首字元為 ?,則可以匹配上,繼續遞迴匹配 s[1:] 和 p[1:]
    • 如果 p 首字元為 * ,則可以嘗試兩種情況:
    • (1)試探讓 * 表示 s 的首字元,s 右移一位,* 保留,繼續遞迴匹配 s[1:] 和 p
    • (2)試探讓 * 表示空字元,略過 *,s不變,繼續遞迴匹配 s 和 p[1:]
    • 這兩種情況是 or 的關係,只要有一個為真,則說明可以匹配上。

2、改進版

沿用上面的遞迴方法的思路,額外開闢一個空間:將上述過程中的每次子字串的匹配結果,專門用一個詞典儲存起來,這樣之後再次用到的話,直接取出即可,不必再計算一遍。

為什麼有可能再次用到?關鍵在於上面說的特殊情況,即 p 的首字元是 * 的情況。這時候會分成兩種情況,兩者是 or 的關係,都需要計算出結果,這時候就可能會重複計算了。

python實現

def isMatch(s, p):
    """
    :type s: str
    :type p: str
    :rtype: bool
    遞迴法。
    """
    if not p:
        return not s
    
    if s: # s不為空
        if p[0] in [s[0], '?']: # 首字元相同,或者p首字元為?,都可以匹配上
            return isMatch(s[1:], p[1:]) # 各右移一位,繼續匹配
        elif p[0] == '*': # p首字元為*
            # isMatch(s[1:], p)試探讓*表示s的首字元,s右移一位,*保留
            # isMatch(s, p[1:])試探讓*表示空字元,略過*,s不變
            return isMatch(s[1:], p) or isMatch(s, p[1:]) 
    elif p[0] == '*': # s為空,則只有p全部為*,才可以匹配上
        return isMatch(s, p[1:])
    
    return False

def isMatch2(s, p):
    """
    :type s: str
    :type p: str
    :rtype: bool
    改進版。
    """
    p.replace('**','*')
    memo = {}
    def dp(i, j):
        if (i, j) not in memo:
            ans = False
            if j == len(p):
                ans = i == len(s)
            elif i < len(s):
                if p[j] in [s[i], '?']:
                    ans = dp(i+1, j+1)
                elif p[j] == '*':
                    if (i, j+1) in memo:
                        ans = dp(i, j+1) or dp(i+1, j)
                    else:
                        ans = dp(i+1, j) or dp(i, j+1) 
            elif p[j] == '*':
                ans = dp(i, j+1)
            memo[i, j] = ans
        return memo[i, j]
    return dp(0, 0)      
    
if '__main__' == __name__:
    s = "adceb"
    p = "*a*b"
    print(isMatch(s, p))