1. 程式人生 > >Leetcode 44:萬用字元匹配(超詳細的解法!!!)

Leetcode 44:萬用字元匹配(超詳細的解法!!!)

給定一個字串 (s) 和一個字元模式 (p) ,實現一個支援 '?''*' 的萬用字元匹配。

'?' 可以匹配任何單個字元。
'*' 可以匹配任意字串(包括空字串)。

兩個字串完全匹配才算匹配成功。

說明:

  • s 可能為空,且只包含從 a-z 的小寫字母。
  • p 可能為空,且只包含從 a-z 的小寫字母,以及字元 ?*

示例 1:

輸入:
s = "aa"
p = "a"
輸出: false
解釋: "a" 無法匹配 "aa" 整個字串。

示例 2:

輸入:
s = "aa"
p = "*"
輸出: true
解釋: '*' 可以匹配任意字串。

示例 3:

輸入:
s = "cb"
p = "?a"
輸出: false
解釋: '?' 可以匹配 'c', 但第二個 'a' 無法匹配 'b'。

示例 4:

輸入:
s = "adceb"
p = "*a*b"
輸出: true
解釋: 第一個 '*' 可以匹配空字串, 第二個 '*' 可以匹配字串 "dce".

示例 5:

輸入:
s = "acdcb"
p = "a*c?b"
輸入: false

解題思路

這個問題和之前問題Leetcode 10:正則表示式匹配(最詳細的解法!!!)很類似,而且比之前的問題要容易,我們只要在之前問題上稍加修改就可以了。我們稍微說一下這個問題的思路,這個問題的難點在於判斷*

匹配多少次的問題。所以我們不妨從最簡單的情況開始考慮,我們首先判斷p的第一個元素是不是*。如果p的第一個元素是*的話,那我們只要考慮*是匹配零次還是匹配一次即可,也就是我們只要判斷isMatch(s[1:],p)isMatch(s,p[1:])

如果p的第一個元素不是*的話,我們只要判斷s[0]p[0]能否匹配。所以我們可以非常迅速的寫出動態規劃轉移方程。

  • f ( i
    , j ) = f ( i , j 1 )   o r   f ( i 1 , j )    i f    p [ j 1 ] = f(i,j)=f(i,j-1)\ or\ f(i-1, j)\ \ if\ \ p[j-1]='*'
  • f ( i , j ) = f ( i 1 , j 1 )    a n d    ( s [ i 1 ] = = p [ j 1 ] p [ j 1 ] = = ? )    i f    p [ j 1 ] f(i,j)=f(i-1,j-1)\ \ and \ \ (s[i-1]==p[j-1] || p[j-1]=='?')\ \ if\ \ p[j-1]\neq'*'

f(i,j)表示輸入s[0:i]和輸入p[0:j]時的匹配結果。具體的思維轉換過程可以閱讀之前的文章。

class Solution:
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        s_len, p_len = len(s), len(p)
        mem = [[False]*(p_len + 1) for _ in range(s_len + 1)]
        mem[0][0] = True
        for i in range(s_len + 1):
            for j in range(1, p_len + 1):
                if p[j-1] == '*':
                    mem[i][j] = mem[i][j-1] or \
                                (i > 0 and mem[i-1][j])
                else:
                    mem[i][j] = i > 0 and\
                                mem[i-1][j-1] and \
                                (s[i-1] == p[j-1] or p[j-1] == "?")

        return mem[-1][-1]

由於這個問題比較簡單,所以我們可以直接從正面去解決它。我們先遍歷sp

s: a d c e b 
   i
p: * a * b
   j

我們發現j指向的元素是*,所以我們記錄下*的位置和此時i的位置,然後我們j++判斷下一個位置。

s: a d c e b 
   i
p: * a * b
     j
star:0
i_index:0

接著我們判斷ij所指向的元素是不是相同,如果是的話我們i++;j++

s: a d c e b 
     i
p: * a * b
       j
star:0
i_index:0

此時j所指向的元素又是*,我們按之前那樣操作。

s: a d c e b 
       i
p: * a * b
         j
star:2
i_index:1

此時,我們發現j既沒有指向*ij所指向的元素又不相等。我們要回過頭來看*,我們此時是知道*的位置的,所以我們直接i++即可,也就是*此時匹配兩次。

s: a d c e b 
         i
p: * a * b
         j
star:2
i_index:1

同上,我們再令i++

s: a d c e b 
           i
p: * a * b
         j
star:2
i_index:1

此時,我們發現ij所指向的元素相同,所以我們i++;j++,此時我們發現匹配結束了,並且匹配成功。

我們回顧一下上面的整個過程,這其中還有一些漏洞,首先如果上述三種情況都不存在,那麼我們直接返回false。如果我們匹配結束後(s匹配完),我們發現j所指向的元素以及其後的所有元素是*以外的元素話,我們返回false

class Solution:
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        s_len, p_len = len(s), len(p)
        i, j, star, i_index = 0, 0, -1, 0
        while i < s_len:
            if j < p_len and (p[j] == '?' or p[j] == s[i]):
                i += 1
                j += 1
            elif j < p_len and p[j] == '*':
                star = j
                j += 1
                i_index = i
            elif star != -1:
                j = star + 1
                i_index += 1
                i = i_index
            else:
                return False
        
        while j < p_len and p[j] == '*':
            j += 1

        return j == p_len

reference:

https://leetcode.com/problems/wildcard-matching/discuss/17810/Linear-runtime-and-constant-space-solution

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

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