Title
給定一個字符串 (s) 和一個字符模式 § ,實現一個支持 ‘?’ 和 ‘*’ 的通配符匹配。
'?' 可以匹配任何單個字符。
'*' 可以匹配任意字符串(包括空字符串)。
兩個字符串完全匹配纔算匹配成功。
說明:
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
動態規劃
Solve
在給定的模式 p 中,只會有三種類型的字符出現:
- 小寫字母 a−z,可以匹配對應的一個小寫字母;
- 問號 ?,可以匹配任意一個小寫字母;
- 星號 *,可以匹配任意字符串,可以爲空,也就是匹配零或任意多個小寫字母。
其中「小寫字母」和「問號」的匹配是確定的,而「星號」的匹配是不確定的,因此我們需要枚舉所有的匹配情況。爲了減少重複枚舉,我們可以使用動態規劃來解決本題。
我們用 dp[i][j] 表示字符串 s 的前 i 個字符和模式 p 的前 j 個字符是否能匹配。
在進行狀態轉移時,我們可以考慮模式 p 的第 j 個字符 pj,與之對應的是字符串 s 中的第 i 個字符 si:
- 如果 pj 是小寫字母,那麼 si 必須也爲相同的小寫字母,狀態轉移方程爲:dp[i][j]=(si 與 pj 相同)∧dp[i−1][j−1]
- 如果 pj 是問號,那麼對 si 沒有任何要求,狀態轉移方程爲:
- 如果 pj 是星號,那麼同樣對 si 沒有任何要求,但是星號可以匹配零或任意多個小寫字母,因此狀態轉移方程分爲兩種情況,即使用或不使用這個星號:dp[i][j]=dp[i][j−1]∨dp[i−1][j]
最終的狀態轉移方程如下:
我們也可以將前兩種轉移進行歸納:
細節
只有確定了邊界條件,才能進行動態規劃。在上述的狀態轉移方程中,由於 dp[i][j] 對應着 s 的前 i 個字符和模式 p 的前 j 個字符,因此所有的 dp[0][j] 和 dp[i][0] 都是邊界條件,因爲它們涉及到空字符串或者空模式的情況,這是我們在狀態轉移方程中沒有考慮到的:
-
dp[0][0]=True,即當字符串 s 和模式 p 均爲空時,匹配成功;
-
dp[i][0]=False,即空模式無法匹配非空字符串;
-
dp[0][j] 需要分情況討論:因爲星號才能匹配空字符串,所以只有當模式 p 的前 j 個字符均爲星號時,dp[0][j] 才爲真。
我們可以發現,dp[i][0] 的值恆爲假,dp[0][j] 在 j 大於模式 p 的開頭出現的星號字符個數之後,值也恆爲假,而 dp[i][j] 的默認值(其它情況)也爲假,因此在對動態規劃的數組初始化時,我們就可以將所有的狀態初始化爲False,減少狀態轉移的代碼編寫難度。
最終的答案即爲 dp[m][n],其中 m 和 n 分別是字符串 s 和模式 p 的長度。需要注意的是,由於大部分語言中字符串的下標從 0 開始,因此 si 和 pj 分別對應着 s[i−1] 和 p[j−1]。
Code
def isMatch_dp(self, s: str, p: str) -> bool:
lengthS, lengthP = len(s), len(p)
dp = [[False] * (lengthP + 1) for _ in range(lengthS + 1)]
dp[0][0] = True
for i in range(1, lengthP + 1):
if p[i - 1] == '*':
dp[0][i] = True
else:
break
for i in range(1, lengthS + 1):
for j in range(1, lengthP + 1):
if p[j - 1] == '*':
dp[i][j] = dp[i][j - 1] or dp[i - 1][j]
elif p[j - 1] == '?' or s[i - 1] == p[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
return dp[lengthS][lengthP]
複雜度分析
時間複雜度:O(mn),其中 m 和 n 分別是字符串 s 和模式 p 的長度。
空間複雜度:O(mn),即爲存儲所有 (m+1)(n+1) 個狀態需要的空間。此外,在狀態轉移方程中,由於 dp[i][j] 只會從 dp[i][…] 以及 dp[i−1][…] 轉移而來,因此我們可以使用滾動數組對空間進行優化,即用兩個長度爲 n+1 的一維數組代替整個二維數組進行狀態轉移,空間複雜度爲 O(n)。