这题面给的真是一坨goushi啊,中英文题面全是错的的地方。
题目大意
给你两个字符串 s s s 和 p p p 其中 p p p 是一个所谓的正则表达式的字符串。 s s s 由小写英文字母组成, p p p 由小写英文字母、‘.’、‘*’ 组成,然后问你 p p p 能不能匹配 s s s。
“.”:可以匹配一个且仅一个字符
“X*”(这里的X表示一个小写英文字母或者’.'):可以匹配零个或者多个 X(可以是多个 ‘.’)(这里的描述和原本题目描述的不一样)
然后问你能不能匹配上,能的话返回一个 true,否则返回一个 false
为了防止大家不懂,这里给大家多来几个样例。
- s:
a
a
a
a\ a\ a
a a a
p: a ∗ a\ * a ∗
这里的 a ∗ a\ * a ∗ 匹配了字符串 s 里面的三个 a,结果是 true - s:
a
a
a
a\ a\ a
a a a
p: . . .\ . . .
这里的每一个 . 只能匹配一个 a,总共只能匹配两个,结果是 false - s:
a
b
c
a\ b\ c
a b c
p: c ∗ a . c c*a\ .\ c c∗a . c
前面的 c ∗ c\ * c ∗ 匹配零个字符,然后 ‘ . . .’ 匹配一个字母 b,结果是 true - s:
a
b
c
d
e
f
g
a\ b\ c\ d\ e\ f\ g
a b c d e f g
p: . ∗ .\ * . ∗
“ . ∗ .\ * . ∗” 相当于七个 ‘ . . .’,结果是 true
大致思路
个人感觉 dp 特别好想,我们用 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 s 的前 i 个字母组成的子串,能不能匹配上 p 的前 j 个子串。是的话是 1,否是 0。
我们双重枚举 i 和 j,i 从 0 开始,j 从 1 开始,因为就如上面的第三个样例一样,p 的前缀(不了解的可以去搜一下)有可能相当于 s 的一个空串。
然后
s
[
i
−
1
]
s[i - 1]
s[i−1](因为字符串下标从 0 开始,所以要减一) 只有一种情况(小写字母)。
但是
p
[
j
−
1
]
p[j - 1]
p[j−1] 的情况就比较多了。
- p [ j − 1 ] = = ′ . ′ p[j - 1] == '.' p[j−1]==′.′: p [ j − 1 ] p[j - 1] p[j−1] 肯定能匹配 s [ i − 1 ] s[i - 1] s[i−1],只要 d p [ i − 1 ] [ j − 1 ] dp[i - 1][j - 1] dp[i−1][j−1] 是 true, 那么 d p [ i ] [ j ] dp[i][j] dp[i][j] 也必定是 true。
- p [ j − 1 ] p[j - 1] p[j−1] 是一个字符:如果 p [ j − 1 ] = = s [ i − 1 ] p[j - 1] == s[i - 1] p[j−1]==s[i−1],那么只要 d p [ i − 1 ] [ j − 1 ] dp[i - 1][j - 1] dp[i−1][j−1] 是 true,那么 d p [ i ] [ j ] dp[i][j] dp[i][j] 肯定也是 true,但是如果 p [ j − 1 ] ≠ s [ i − 1 ] p[j - 1] \ne s[i - 1] p[j−1]=s[i−1] 那么 d p [ i ] [ j ] dp[i][j] dp[i][j] 肯定是 false。
- p [ j − 1 ] = = ′ ∗ ′ p[j - 1] == '*' p[j−1]==′∗′:这种情况比较麻烦,首先会有 ‘ ∗ * ∗’ 和 它前面的字母相当于空串的情况,即:只要 d p [ i ] [ j − 2 ] dp[i][j - 2] dp[i][j−2] 是 true,那么 d p [ i ] [ j ] dp[i][j] dp[i][j] 肯定也是 true;如果 p [ j − 2 ] p[j - 2] p[j−2] 是一个字母或者 ‘.’ 的话,那么就要分情况讨论了,大致分为以下五种情况:
- 当
p
[
j
−
2
]
≠
s
[
i
−
1
]
p[j - 2] \ne s[i - 1]
p[j−2]=s[i−1] AND
p
[
j
−
2
]
≠
′
.
′
p[j - 2] \ne '.'
p[j−2]=′.′:根本匹配不上,
p
[
j
−
1
]
p[j - 1]
p[j−1] 和
p
[
j
−
2
]
p[j - 2]
p[j−2] 只能充当空串,不用管;
- 当
p
[
j
−
2
]
=
s
[
i
−
1
]
p[j - 2] = s[i - 1]
p[j−2]=s[i−1] AND
p
[
j
−
1
]
p[j - 1]
p[j−1] 和
p
[
j
−
2
]
p[j - 2]
p[j−2] 匹配不止一个字符:
因为我们我们枚举的顺序是从小到大,例如 d p [ i − 1 ] [ j ] dp[i - 1][j] dp[i−1][j] 的情况我们是知道的,那么此时只要 d p [ i − 1 ] [ j ] dp[i - 1][j] dp[i−1][j] 是 true,就可以确定当前的状态是这种情况,我们直接让 d p [ i ] [ j ] dp[i][j] dp[i][j] 等于 true;
-
当 p [ j − 2 ] = s [ i − 1 ] p[j - 2] = s[i - 1] p[j−2]=s[i−1] AND p [ j − 1 ] p[j - 1] p[j−1] 和 p [ j − 2 ] p[j - 2] p[j−2] 且只匹配一个字符:
只要 d p [ i − 1 ] [ j − 2 ] dp[i - 1][j - 2] dp[i−1][j−2] 是 true,那么我们就可以判断当前状态是这种情况,让 d p [ i ] [ j ] dp[i][j] dp[i][j] 等于 true; -
- 第四种和第五种情况就是 p [ j − 2 ] = ′ . ′ p[j - 2] = '.' p[j−2]=′.′ 但是 p [ j − 2 ] p[j - 2] p[j−2] 和 p [ j − 1 ] p[j - 1] p[j−1] 匹配多个和只匹配一个字符,两种情况,不需要判断 s [ i − 1 ] s[i - 1] s[i−1] 和 p [ j − 2 ] p[j - 2] p[j−2],剩下的和情况 2、3 类似。看看 d p [ i − 1 ] [ j ] dp[i - 1][j] dp[i−1][j] 和 d p [ i − 1 ] [ j − 2 ] dp[i - 1][j - 2] dp[i−1][j−2] 是不是 true 就行了。
最后答案是 d p [ s . s i z e ( ) ] [ p . s i z e ( ) ] dp[s.size()][p.size()] dp[s.size()][p.size()],时间复杂度就是双重枚举的复杂度。
代码
我感觉 vector 好难用啊,OIer 表示不是很适应。
class Solution {
public:
bool isMatch(string s, string p) {
int n = s.size();
int m = p.size();
auto isLetter = [&](char c){ // 判断是不是小写字母
return (c >= 'a' && c <= 'z');
};
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
dp[0][0] = 1;
for (int i = 0; i <= n; i++) // 记得这里是从 0 开始
for (int j = 1; j <= m; j++)
{
if (p[j - 1] == '*') // 这里写的有点重复了,大家可以把他简化一下
{
dp[i][j] |= dp[i][j - 2]; // '*' 和它前面的字母相当于空串的情况
if (i != 0 && j != 1 && p[j - 2] == s[i - 1])
dp[i][j] |= dp[i - 1][j], dp[i][j] |= dp[i - 1][j - 2]; // 情况 2 和 3
if (i != 0 && p[j - 2] == '.')
dp[i][j] |= dp[i - 1][j], dp[i][j] |= dp[i - 1][j - 2]; // 情况 4 和 5
}
else
{
if (i == 0) // 注意不要越界
continue;
if (isLetter(p[j - 1]))
{
if (p[j - 1] != s[i - 1]) // 匹配不上直接 false
dp[i][j] = false;
else // 匹配上了就看看前面能不能匹配上
dp[i][j] |= dp[i - 1][j - 1];
}
else // 因为 p[j - 1] 是 '.' 无论 s[i - 1] 是啥都无所谓,直接看前面
dp[i][j] |= dp[i - 1][j - 1];
}
}
return dp[n][m];
}
};
结果
我发现我每次写 hard 用时都比别人大。
作者能力有限,如果有任何错误之处,还请各位指教。(~ ̄▽ ̄)~