正则表达式匹配
思路1:递归回溯
难点: *
出现的次数
总体思路:通过不断的剪去s和p相同的首部,直到某一个或两个都被剪空,就可以得到结论。
大致可以分为两种情况:包含*
,不包含*
- 不包含
*
(最简单):只需要依次比较
if(s[i] == p[i] || p[i] == '.') {
return isMatch(s.substr(1), p.substr(1));
}
- 包含
*
:- p的第i个元素在s中出现0次:保持 s 不变,将 p 剪去两个元素,继续调用isMatch。
- p的第i个元素在s中出现一次或者多次:若 p 和 s 首元素相同,则保持 p 不变,将 s 剪去一个元素
代码如下:
class Solution {
public:
bool isMatch(string s, string p) {
if(p.empty()) return s.empty();
bool first_match = !s.empty() && (s[0] == p[0] || p[0] == '.');
if(p.size() >= 2 && p[1] == '*') {
return (isMatch(s, p.substr(2)) || first_match && isMatch(s.substr(1), p));
} else {
return first_match && isMatch(s.substr(1), p.substr(1));
}
}
}
- 方法1因每种匹配方式都会尝试,导致重复多余的比较
- 理论时间复杂度:~= O( 2 m a x ( m , n ) 2^{max(m,n)} 2max(m,n))
- 空间复杂度:~= O( 2 m a x ( m , n ) 2^{max(m,n)} 2max(m,n))
不知为啥效率很低,希望后期能优化
思路2:动态规划
dp[i][j] 表示 s 的前 i 个字符与 p 中的前 j 个字符是否匹配
写法一:
时间复杂度:O(mn),空间复杂度:O(mn)
class Solution {
public:
bool first_match(string s, string p, int i, int j) {
return s[i] == p[j] || p[j] == '.';
}
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
bool dp[m+1][n+1];
memset(dp, false, sizeof dp);
for(int i = 2; i <= n; ++i) dp[0][i] = p[i-1] == '*' && dp[0][i-2];
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(p[j] == '*') {
//
dp[i+1][j+1] = dp[i+1][j-1] || first_match(s, p, i, j-1) && dp[i][j+1];
} else {
dp[i+1][j+1] = first_match(s, p, i, j) && dp[i][j];
}
}
}
return dp[m][n];
}
}
同样的问题,效率极低?
- 效率低的原因:有很多无效判断!!!
写法二:
分析:
难点: *
表示前一个字符出现零次或者多次,复杂的地方就在于 *
的不唯一性。
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 s s s 的前 i i i 个字符与 p p p 中的前 j j j 个字符是否能够匹配
考虑 p p p 的第 j j j 个字符的匹配情况:
- 如果
p
p
p 的第
j
j
j 个字符是一个小写字母:那么,必须在
s
s
s 中匹配一个相同的字母,即:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] , s [ i ] = p [ j ] f a l s e , s [ i ] ≠ p [ j ] dp[i][j] = \left\{\begin{matrix} dp[i-1][j-1], \qquad s[i]=p[j]\\false,\qquad \qquad \qquad s[i]\ne p[j]\end{matrix}\right. dp[i][j]={dp[i−1][j−1],s[i]=p[j]false,s[i]=p[j]
就是说此种情况下:
如果 s[i] != p[j] ,那么dp[i][j] = false;
如果 s[i] == p[j],那么dp[i][j] 取决于 dp[i-1][j-1] 的匹配情况
- 如果
p
p
p 的第
j
j
j 个字符是
*
:那么就表示我们可以对 p p p 的第 j − 1 j−1 j−1 个字符匹配任意自然数次。
- 匹配 0 次:此时,
p
p
p 中 ‘字符 +
*
’ 没有匹配任何 s s s 中的字符。
d p [ i ] [ j ] = d p [ i ] [ j − 2 ] dp[i][j] = dp[i][j-2] dp[i][j]=dp[i][j−2] - 匹配 1、2、3…次:
d p [ i ] [ j ] = d p [ i − 1 ] [ j − 2 ] , i f s [ i ] = p [ j − 1 ] dp[i][j] = dp[i-1][j-2],\qquad if \quad s[i]=p[j-1] dp[i][j]=dp[i−1][j−2],ifs[i]=p[j−1]
d p [ i ] [ j ] = d p [ i − 2 ] [ j − 2 ] , i f s [ i − 1 ] = s [ i ] = p [ j − 1 ] dp[i][j] = dp[i-2][j-2],\qquad if \quad s[i-1]=s[i]=p[j-1] dp[i][j]=dp[i−2][j−2],ifs[i−1]=s[i]=p[j−1]
d p [ i ] [ j ] = d p [ i − 3 ] [ j − 2 ] , i f s [ i − 2 ] = s [ i − 1 ] = s [ i ] = p [ j − 1 ] dp[i][j] = dp[i-3][j-2],\qquad if \quad s[i-2]=s[i-1]=s[i]=p[j-1] dp[i][j]=dp[i−3][j−2],ifs[i−2]=s[i−1]=s[i]=p[j−1]
. . . . . . . . . . . . ............ ............
此时,状态转移方程如下:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
−
1
]
[
j
]
o
r
d
p
[
i
]
[
j
−
2
]
,
s
[
i
]
=
p
[
j
−
1
]
d
p
[
i
]
[
j
−
2
]
,
s
[
i
]
≠
p
[
j
−
1
]
dp[i][j] = \left\{\begin{matrix} dp[i-1][j]\quad or\quad dp[i][j-2], \qquad s[i]=p[j-1]\\ dp[i][j-2], \quad \quad \qquad \qquad \qquad \qquad s[i]\ne p[j-1]\end{matrix}\right.
dp[i][j]={dp[i−1][j]ordp[i][j−2],s[i]=p[j−1]dp[i][j−2],s[i]=p[j−1]
解释:
\qquad
若
s
[
i
]
≠
p
[
j
−
1
]
s[i] \ne p[j-1]
s[i]=p[j−1],此时,只能匹配 0 次,
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 取决于
d
p
[
i
]
[
j
−
2
]
dp[i][j-2]
dp[i][j−2]
\qquad
若
s
[
i
]
=
p
[
j
−
1
]
s[i] = p[j-1]
s[i]=p[j−1],此时,
\qquad \qquad
情况1:匹配
s
s
s 末尾的一个字符,将该字符扔掉,而该组合还可以继续进行匹配;
\qquad \qquad
情况2:不匹配字符,将该组合扔掉,不再进行匹配。
- 如果
p
p
p 的第
j
j
j 个字符是
.
:那么 p [ j ] p[j] p[j] 一定成功匹配 s s s 中的任意一个小写字母
最终的状态转移方程如下:
d
p
=
f
dp = f
dp=f
代码如下:
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
// 为了判断s[i]和p[j]是否能够匹配(lambda匿名函数写法)
auto matches = [&]() {
// s、p 的范围都是[1, 20]
/*
注意下标,全局的 i,j 表示dp数组中的行列
所以dp[i][j]其实表示 s[i-1] 与 p[j-1] 是否能够匹配。
*/
if(i == 0) return false; // s[0][j] === false;
if(p[j-1] == '.') return true; // 当p[j-1] = '.', 匹配任意字符
return s[i-1] == p[j-1]; // s[i] == p[j]
};
bool dp[m+1][n+1];
memset(dp, false, sizeof dp);
dp[0][0] = true; // s = ‘’,p = ‘’
for (int i = 0; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
// --> dp[i][j]
// 两种情况:1. p 当前字符(p[j-1])是‘*’;2. 不是‘*’
if (p[j-1] == '*') {
/*
为啥是 dp[i][j] |= ....,为啥是与等于?(注意状态转移方程)
可能是为了提高效率?
如果是先写 dp[i][j] = dp[i][j-2];则要 |=
否则,不需要
*/
dp[i][j] |= dp[i][j-2]; // 此时 * 表示匹配 0 次
if (matches(i, j-1)) dp[i][j] |= dp[i-1][j];
} else {
if (matches(i, j)) dp[i][j] = dp[i-1][j-1];
}
}
}
return dp[m][n];
}
}