题目描述
给定一个字符串s和一个字符规律p,请你来实现一个支持'.'和'*'的正则表达式匹配。
'.'匹配任意单个字符
'*'匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串s的,而不是部分字符串。
难度:困难
示例1:
输入:s="aa",p="a"
输出:false
解释:"a"无法匹配"aa"整个字符串
示例2:
输入:s="aa",p="a*"
输出:true
解释:因为'*'代表可以匹配零个或多个前面的那一个元素,在这里前面的元素就是'a'。因此,字符串"aa"可被视为'a'重复了一次。
示例3:
输入:s="ab",p=".*"
输出:true
解释:".*"表示可匹配零个或多个('*')任意字符('.'),'.'匹配a,然后'*'先匹配'.',然后再转换为b
提示
- 1<=s.length<=20
- 1<=p.length<=20
- s只包含从a-z的小写字母
- p只包含从a-z的小写字母,以及字符.和*
- 保证每次出现字符*时,前面都匹配到有效的字符
解题思路:动态规划
- 从左往右扫的话,字符后面是否跟着星号会影响结果,分析起来有点复杂。
例如:s="aab",p="c*a*b"
是否会思考,a与c不匹配,故直接输出false。
一定要考虑后面是否还跟着'*'
c*匹配的可以是零个c,这样s和p就完全匹配了,最后将输出true
- 从右往左扫描,星号的前面肯定有一个字符,星号也只影响这一个字符,它就像一个拷贝器。
例如:s="aab",p="c*a*b"
b和b对应,然后a*和aa对应,最后c*和' '对应。
s和p串是否匹配,取决于:最右端是否匹配、剩余的子串是否匹配。
只是最右端可能是特殊符号,需要分情况讨论。
案例分析
- p为空串,s不为空串,肯定不匹配
- s为空串,但p不为空串,要想匹配,只可能是右端是星号,它干掉一个字符后,将p变为空串。
- s、p都为空串,肯定匹配。
- s、p都不为空串,从右侧开始,依次对比右侧子串和剩余子串是否都匹配。
定义f[i][j]表示s的前i个字符和p的前j个字符是否能够匹配。
- 如果p的第j个字符是一个小写字母,那么我们必须在s中匹配一个相同的小写字母,即:
- 如果p的第j个字符是*,那么就表示我们可以对p的第j-1个字符匹配任意自然数次。根据上文所述,字母+星号的组合在匹配的过程中,本质上只会存在两种情况:
- 匹配s末尾的一个字符,将该字符去除,而该组合还可以继续进行匹配。
- 不匹配字符,将该组合去除,不再进行匹配。根据上述分析,可以写出精巧的状态转移方程:
- 在任意情况下,只要p[j]是'.',那么p[j]一定匹配s中的任意一个小写字母。最终的状态转移方程如下:
注意:
动态规划的边界条件为f[0][0]=true,即两个空字符串是可以匹配的。最终的答案即为f[m][n],其中m和n分别代表的是s和p的长度。由于大部分语言中,字符串的字符下标是从0开始的,因此在实现上面的状态转移方程时,需要注意状态中每一维下标与实际字符下标的对应关系。
class Solution{
public:
bool isMatch(string s,string p){
int m=s.size(),n=p.size();//m和n分别代表字符串s和p的长度
auto matches = [&](int i,int j)
{
if(i==0)
return false;
if(p[j-1]=='.')
return true;
return s[i-1]==p[j-1];
}
vector<vector<int>> f(m+1,vector<int>(n+1));//定义f[m+1][n+1]
f[0][0]=true;//定义初始值为true或1
for(int i=0;i<=m;i++)
{
for(int j=0;j<=n;j++)
{
if(p[j-1]=='*')
f[i][j] |= f[i][j-2];
if(matches(i,j-1))//p[j-1]=='.'
f[i][j] |= f[i-1][j];
else
if(matches(i,j))
f[i][j] |= f[i-1][j-1];
}
}
return f[m][n];
}
};
复杂度分析:
时间复杂度:O(mn),其中m和n分别是字符串s和p的长度。
空间复杂度:O(mn),即为存储所有状态使用的空间。