一、题目描述
请实现一个函数用来匹配包含’. ‘和’*‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但与"aa.a"和"ab*a"均不匹配。
示例1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例2:
输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例3:
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
示例4:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例5:
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
注:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 ‘*’。
二、思路分析
注:思路分析中的一些内容和图片参考自力扣各位前辈的题解,感谢他们的无私奉献
思路
假设主串为 A A A,长度为 n n n。模式串为 B B B,长度为 m m m。需要关注正则表达式 B B B 的最后一个字符是谁,它有三种可能:
正常字符
、∗
、.
那针对这三种情况讨论即可,如下:
①如果 B B B 的最后一个字符是正常字符
,那就是看 A [ n − 1 ] A[n-1] A[n−1] 是否等于 B [ m − 1 ] B[m-1] B[m−1]。相等则看 A 0.. n − 2 A_{0..n-2} A0..n−2 与 B 0.. m − 2 B_{0..m-2} B0..m−2。不等则是不能匹配,这就是子问题。
②如果 B B B 的最后一个字符是.
,它能匹配任意字符,直接看 A 0.. n − 2 A_{0..n-2} A0..n−2 与 B 0.. m − 2 B_{0..m-2} B0..m−2
③如果 B B B 的最后一个字符是*
,它代表 B [ m − 2 ] = c B[m-2]=c B[m−2]=c 可以重复0次或多次,它们是一个整体 c ∗ c* c∗
----情况1: A [ n − 1 ] A[n-1] A[n−1] 是 0 0 0 个 c c c, B B B 最后两个字符废了,能否匹配取决于 A 0.. n − 1 A_{0..n-1} A0..n−1 和 B 0.. m − 3 B_{0..m-3} B0..m−3 是否匹配
----情况2: A [ n − 1 ] A[n-1] A[n−1] 是多个 c c c 中的最后一个(这种情况必须 A [ n − 1 ] = c A[n-1]=c A[n−1]=c 或者 c = . c=. c=.),所以 A A A 匹配完往前挪一个, B B B 继续匹配,因为可以匹配多个,继续看 A 0.. n − 2 A_{0..n-2} A0..n−2 和 B 0.. m − 1 B_{0..m-1} B0..m−1 是否匹配。
转移方程
f [ i ] [ j ] f[i][j] f[i][j] 代表 A A A 的前 i i i 个和 B B B 的前 j j j 个能否匹配
①对于前面两个情况,可以合并成一种情况 f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] f[i][j] = f[i-1][j-1] f[i][j]=f[i−1][j−1]
②对于第三种情况,对于 c ∗ c* c∗ 分为看和不看两种情况
----不看:直接砍掉正则串的后面两个, f [ i ] [ j ] = f [ i ] [ j − 2 ] f[i][j] = f[i][j-2] f[i][j]=f[i][j−2]
----看:正则串不动,主串前移一个, f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i-1][j] f[i][j]=f[i−1][j]
初始条件
特判:需要考虑空串空正则
----空串和空正则是匹配的, f [ 0 ] [ 0 ] = t r u e f[0][0] = true f[0][0]=true
----空串和非空正则,不能直接定义 t r u e true true 和 f a l s e false false,必须要计算出来。(比如 A = ′ ′ ′ ′ A= '''' A=′′′′, B = a ∗ b ∗ c ∗ B=a*b*c* B=a∗b∗c∗)
----非空串和空正则必不匹配, f [ 1 ] [ 0 ] = . . . = f [ n ] [ 0 ] = f a l s e f[1][0]=...=f[n][0]=false f[1][0]=...=f[n][0]=false
----非空串和非空正则,那肯定是需要计算的了。
大体上可以分为空正则和非空正则两种,空正则也是比较好处理的,对非空正则我们肯定需要计算。非空正则的三种情况,前面两种可以合并到一起讨论,第三种情况是单独一种,那么也就是分为当前位置是 ∗ 和不是 ∗ 两种情况了。
结果
我们开数组要开 n + 1 n+1 n+1,这样对于空串的处理十分方便,结果就是 f [ n ] [ m ] f[n][m] f[n][m]
复杂度分析:
时间复杂度 O ( M N ) \rm{O(MN)} O(MN):其中M
、N
分别为s
和p
的长度,状态转移需遍历整个dp矩阵。
空间复杂度 O ( M N ) \rm{O(MN)} O(MN):状态矩阵dp使用O(MN)
的额外空间。
三、整体代码
整体代码如下
bool isMatch(char* s, char* p){
int n = strlen(s);
int m = strlen(p);
bool f[n + 1][m + 1];
memset(f, false, (n + 1) * (m + 1) * sizeof(bool));
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
//分成空正则和非空正则两种
if (j == 0) {
f[i][j] = i == 0; //空正则空串,匹配。 空正则非空串,不匹配
} else {
//非空正则分为两种情况 * 和 非*
if (p[j-1] != '*') { //非*
//如果s和p当前字符匹配的上 或者 p当前字符是个 '.',则看下一个字符
if (i > 0 && (s[i-1] == p[j-1] || p[j-1] == '.')) {
f[i][j] = f[i - 1][j - 1];
}
} else {
//碰到 * 了,分为看和不看两种情况
//不看,直接砍掉正则串的后面两个
if (j >= 2) {
f[i][j] |= f[i][j - 2];
}
//看,正则串不动,主串前移一个
if (i >= 1 && j >= 2 && (s[i-1] == p[j-2] || p[j-2] == '.')) {
f[i][j] |= f[i - 1][j];
}
}
}
}
}
return f[n][m];
}
运行,测试通过