44. Wildcard Matching 通配符匹配
Title
給定一個字符串 (s) 和一個字符模式 § ,實現(xiàn)一個支持 ‘?’ 和 ‘*’ 的通配符匹配。
'?' 可以匹配任何單個字符。 '*' 可以匹配任意字符串(包括空字符串)。兩個字符串完全匹配才算匹配成功。
說明:
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(chǎn)’ 無法匹配 ‘b’。
示例 4:
輸入:
s = "adceb" p = "*a*b"輸出: true
解釋: 第一個 ‘’ 可以匹配空字符串, 第二個 '’ 可以匹配字符串 “dce”.
示例 5:
輸入:
s = "acdcb" p = "a*c?b"輸出: false
動態(tài)規(guī)劃
Solve
在給定的模式 p 中,只會有三種類型的字符出現(xiàn):
- 小寫字母 a?z,可以匹配對應(yīng)的一個小寫字母;
- 問號 ?,可以匹配任意一個小寫字母;
- 星號 *,可以匹配任意字符串,可以為空,也就是匹配零或任意多個小寫字母。
其中「小寫字母」和「問號」的匹配是確定的,而「星號」的匹配是不確定的,因此我們需要枚舉所有的匹配情況。為了減少重復(fù)枚舉,我們可以使用動態(tài)規(guī)劃來解決本題。
我們用 dp[i][j] 表示字符串 s 的前 i 個字符和模式 p 的前 j 個字符是否能匹配。
在進行狀態(tài)轉(zhuǎn)移時,我們可以考慮模式 p 的第 j 個字符 pj,與之對應(yīng)的是字符串 s 中的第 i 個字符 si:
- 如果 pj 是小寫字母,那么 si 必須也為相同的小寫字母,狀態(tài)轉(zhuǎn)移方程為:dp[i][j]=(si 與 pj 相同)∧dp[i?1][j?1]
- 如果 pj 是問號,那么對 si 沒有任何要求,狀態(tài)轉(zhuǎn)移方程為:
- 如果 pj 是星號,那么同樣對 si 沒有任何要求,但是星號可以匹配零或任意多個小寫字母,因此狀態(tài)轉(zhuǎn)移方程分為兩種情況,即使用或不使用這個星號:dp[i][j]=dp[i][j?1]∨dp[i?1][j]
最終的狀態(tài)轉(zhuǎn)移方程如下:
dp[i][j]={(si與pj相同)∧dp[i?1][j?1],pj是小寫字母dp[i?1][j?1],pj是問號dp[i][j?1]∨dp[i?1][j],pj是星號dp[i][j]= \begin{cases} (s_i與p_j相同)∧dp[i?1][j?1], & p_j 是小寫字母 \\ dp[i?1][j?1], & p_j 是問號 \\ dp[i][j?1]∨dp[i?1][j], & p_j 是星號 \end{cases} dp[i][j]=??????(si?與pj?相同)∧dp[i?1][j?1],dp[i?1][j?1],dp[i][j?1]∨dp[i?1][j],?pj?是小寫字母pj?是問號pj?是星號?
我們也可以將前兩種轉(zhuǎn)移進行歸納:
dp[i][j]={dp[i?1][j?1],si與pj相同或者pj是問號dp[i][j?1]∨dp[i?1][j],pj是星號False,其它情況dp[i][j]= \begin{cases} dp[i?1][j?1], & s_i與p_j相同或者p_j 是問號 \\ dp[i][j?1]∨dp[i?1][j], & p_j 是星號 \\ False, & 其它情況 \end{cases} dp[i][j]=??????dp[i?1][j?1],dp[i][j?1]∨dp[i?1][j],False,?si?與pj?相同或者pj?是問號pj?是星號其它情況?
細(xì)節(jié)
只有確定了邊界條件,才能進行動態(tài)規(guī)劃。在上述的狀態(tài)轉(zhuǎn)移方程中,由于 dp[i][j] 對應(yīng)著 s 的前 i 個字符和模式 p 的前 j 個字符,因此所有的 dp[0][j] 和 dp[i][0] 都是邊界條件,因為它們涉及到空字符串或者空模式的情況,這是我們在狀態(tài)轉(zhuǎn)移方程中沒有考慮到的:
-
dp[0][0]=True,即當(dāng)字符串 s 和模式 p 均為空時,匹配成功;
-
dp[i][0]=False,即空模式無法匹配非空字符串;
-
dp[0][j] 需要分情況討論:因為星號才能匹配空字符串,所以只有當(dāng)模式 p 的前 j 個字符均為星號時,dp[0][j] 才為真。
我們可以發(fā)現(xiàn),dp[i][0] 的值恒為假,dp[0][j] 在 j 大于模式 p 的開頭出現(xiàn)的星號字符個數(shù)之后,值也恒為假,而 dp[i][j] 的默認(rèn)值(其它情況)也為假,因此在對動態(tài)規(guī)劃的數(shù)組初始化時,我們就可以將所有的狀態(tài)初始化為False,減少狀態(tài)轉(zhuǎn)移的代碼編寫難度。
最終的答案即為 dp[m][n],其中 m 和 n 分別是字符串 s 和模式 p 的長度。需要注意的是,由于大部分語言中字符串的下標(biāo)從 0 開始,因此 si 和 pj 分別對應(yīng)著 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] = Truefor i in range(1, lengthP + 1):if p[i - 1] == '*':dp[0][i] = Trueelse:breakfor 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]復(fù)雜度分析
時間復(fù)雜度:O(mn),其中 m 和 n 分別是字符串 s 和模式 p 的長度。
空間復(fù)雜度:O(mn),即為存儲所有 (m+1)(n+1) 個狀態(tài)需要的空間。此外,在狀態(tài)轉(zhuǎn)移方程中,由于 dp[i][j] 只會從 dp[i][…] 以及 dp[i?1][…] 轉(zhuǎn)移而來,因此我們可以使用滾動數(shù)組對空間進行優(yōu)化,即用兩個長度為 n+1 的一維數(shù)組代替整個二維數(shù)組進行狀態(tài)轉(zhuǎn)移,空間復(fù)雜度為 O(n)。
總結(jié)
以上是生活随笔為你收集整理的44. Wildcard Matching 通配符匹配的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 3.Vue 条件渲染
- 下一篇: 什么是Django?