44. 通配符匹配
给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。
'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。
说明:
- s 可能为空,且只包含从 a-z 的小写字母。
- p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。
示例:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。输入:
s = "aa"
p = "*"
输出: true
解释: '*' 可以匹配任意字符串。
分析:
与10. 正则表达式匹配很像,这里贴出第10题与本题的区别:
'*' 匹配零个或多个前面的那一个元素
即第10题需要给出a*才能匹配aaaa,而本题只需要给出a
这样明显降低了难度,我们不再需要考虑*之前的字符了。
虽然之前做过第10题,还是想自己顺一遍动态规划的过程:
- 写出dp结构
- 抓住转移方程;
- 考虑边界条件;
对于本题,首先考虑如何规划dp数组。假设dp[i],可以表示为s字符串的前i个字符可以被p字符串匹配,或者p字符串的前i个字符可以匹配s字符串。由于本题要求 两个字符串都要完全匹配,显然不满足要求。所以需要二重dp数组 dp[i][j],表示 s字符串前i个字符可以被p字符串前j个字符匹配。
接下来考虑转移方程,①:p[j] != '*'时,只需要考虑s[i] == p[j] or p[j] == '?'
表示单个字符被匹配。
即有:dp[i][j] == (s[i] == p[j] or p[j] == '?') and dp[i - 1][j - 1] # p[j] != "*"
②:如果考虑 p[j] == '*',这时有两种情况
- 例如:s = "abcdef", p = "abc*"
这时*起到了匹配多个字符的作用,此时只需要考察p字符串*前的字符是否都被匹配,不断向下即可。dp[i][j] = dp[i - 1][j]
- 例如:s = "abcdef", p = "abc*def"
*相当于空字符,直接跳过不管。dp[i][j] = dp[i][j - 1]
总结:
\(dp[i][j] = \begin{cases}(s[i]==p[j])\ and\ dp[i-1][j-1] &\text{if p[j] != '*'}\\ dp[i-1][j]\ or\ dp[i][j-1] &\text{if p[j] == '*'}\end{cases}\)
最后考虑边界条件:
- 如果s,p都为空,符合要求,即
dp[0][0] == true
; - 由于*可以匹配空字符,因此
dp[0][j] == true if p[j] == '\*' and dp[0][j - 1]
代码(Golang):
func isMatch(s string, p string) bool {
m, n := len(s), len(p)
dp := make([][]bool, m + 1)
for i := 0; i <= m; i++ {
dp[i] = make([]bool, n + 1)
}
dp[0][0] = true
for i := 0; i <= n; i++ {
if p[i - 1] == '*' {
dp[0][i] = true
} else {
break
}
}
for i := 1; i <= m; i++ {
for j := 1;j <= n; j++ {
if p[i - 1] == '*' {
dp[i][j] = dp[i][j - 1] || dp[i - 1][j]
} else if p[j - 1] == '?' || s[i - 1] == p[j - 1] {
dp[i][j] = dp[i - 1][j - 1]
}
}
}
return dp[m][n]
}