echo

任生命穿梭 时间的角落

0%

通配符匹配

44. 通配符匹配

给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?''*' 的通配符匹配。

1
2
'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。

两个字符串完全匹配才算匹配成功。

说明:

  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 ?*

示例 1:

1
2
3
4
5
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

1
2
3
4
5
输入:
s = "aa"
p = "*"
输出: true
解释: '*' 可以匹配任意字符串。

示例 3:

1
2
3
4
5
输入:
s = "cb"
p = "?a"
输出: false
解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。

示例 4:

1
2
3
4
5
输入:
s = "adceb"
p = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".

示例 5:

1
2
3
4
输入:
s = "acdcb"
p = "a*c?b"
输出: false

在匹配的过程中我们可以分为两类:

  • 单字符匹配,s 和 p 中的字母相等,或 p 中的一个 ?匹配任意一个字母。
  • 多字符匹配,p 中的 * 匹配 s 中任意多个字母。

由于多字符匹配的存在我们需要枚举所有匹配的情况,我们用 dp[i][j] 来表示字符串 s 的前 i 个字符和模式 p 的前 j 个字符是否能匹配。在进行转移的时候,我们可以考虑模式 p 的第 j 个字符 pj,

与之对应的是字符串 s 中第 i 个字符 si:

  • pj 是小写字母,那么 si 也必须为小写字母,dp[i][j] = (pj == si) && dp[i - 1][j - 1]
  • pj 是问号,任意匹配 si,dp[i][j] = dp[i - 1][j - 1]
  • pj 是星号,可以匹配零或任意多个小写字母,当匹配 s 中零个小写字母时:dp[i][j] = dp[i][j - 1],不使用这个星号;当匹配 s 中任意多个小写字母时,dp[i][j] = dp[i - 1][j],使用星号匹配 s 中的一个字符(星号未被去掉,可以继续匹配)。

我们需要处理边界情况:

  1. 当 s 和 p 全为空,返回true,dp[0][0] = true;
  2. p 为空,s 不为空,dp[i][0] = false (1 <= i <= p.length)
  3. s 为空,p 不为空,当且仅当 p 中全为星号时才为 true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
boolean[][] dp = new boolean[m + 1][n + 1];
//s 和 p 都为空
dp[0][0] = true;
//s 为空,p 不为空
for(int i = 1; i <= n; i++){
if(p.charAt(i - 1) == '*'){
dp[0][i] = true;
}else{
break;
}
}

for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
//pj 为字母或问号
if(s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '?'){
dp[i][j] = dp[i - 1][j - 1];
}
//pj 为星号
if(p.charAt(j - 1) == '*'){
dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
}
}
}

return dp[m][n];
}
}

时间复杂度O(mn),空间复杂度O(mn)。