题目
给你一个字符串 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
解释:".” 表示可匹配零个或多个(’*’)任意字符(’.’)。
示例 4:
输入:s = “aab” p = “cab”
输出:true
解释:因为 ‘*’ 表示零个或多个,这里 ‘c’ 为 0 个, ‘a’ 被重复一次。因此可以匹配字符串 “aab”。
示例 5:
输入:s = “mississippi” p = “misisp*.”
输出:false
解答
思路
动态规划。
代码中注释已经将细节讲的非常清楚,此处不再赘述。
需要的头文件
string、vector
代码
/*
* @lc app=leetcode.cn id=10 lang=cpp
*
* [10] 正则表达式匹配
*/
// @lc code=start
#include <vector>
#include <string>
using namespace std;
class Solution
{
public:
bool isMatch(string s, string p)
{
int slength = s.size();
int plength = p.size();
//定义动态规划矩阵,注意这里我们矩阵也是用数学上的计算,所以下标应该再多一个
vector<vector<bool>> dp(slength + 1);
for (int i = 0; i < dp.size(); i++)
{
dp[i].resize(plength + 1);
}
// 先填上可以填的
// 如果i,j都是0,那么两个空字符串一定匹配
// 如果i是0,j是1,那么非空字符串(且第一位一定不是*)一定不匹配空字符串
dp[0][0] = true;
dp[0][1] = false;
//i=0的一行是有规律的,因为是非空字符串匹配空字符串,
//只有x*这样的子串才能自己消掉自己,这个字串长度是2,
//所以结果一定与dp[i][j-2]匹配
for (int j = 2; j <= plength; j++)
{
dp[0][j] = dp[0][j - 2] && p[j - 1] == '*';
}
// 如果i≠0,j是0,那么空字符串一定不匹配不为空的字符串
for (int i = 1; i <= slength; i++)
{
dp[i][0] = false;
}
//i=0行和j=0列都填完了,这里要开始按方法填表
//!!!注意,这里矩阵是数学形式,而字符串还是用的下标
//!!!所以,举个例,当dp的i取1的时候,s[i]应该取0,j同理
for (int i = 1; i <= slength; i++)
{
for (int j = 1; j <= plength; j++)
{
//1.匹配情况:普通字母匹配相等,或者是通配符.号,就看前面的那段是否匹配
if (p[j - 1] == s[i - 1] || p[j - 1] == '.')
{
dp[i][j] = dp[i - 1][j - 1];
}
//2.*号情况:分类讨论
else if (p[j - 1] == '*')
{
//假如字符串p是ab*,对字符串s进行匹配,s的情况如下
//3.1 s是a,那么ab*应该减去2个字符进行匹配(b*匹配0个b,自己消掉自己),这是dp[i][j - 2]的情况
//3.2 s是ab,那么ab*应该减去1个字符进行匹配(b*匹配1个b,把*号删掉就可以了),这是dp[i][j - 1]的情况
//3.3 s是abb或者abbb等,那么这类可以看作ab*与a(n个b,n>=2)进行匹配
//我们应该参考ab*与a(n-1个b)的匹配结果,毕竟后面多一个b是不影响*号的多个作用的
//比如ab*与abb,就应该参考ab*与ab是否匹配。所以看出规律,是dp[i][j]=dp[i-1][j]
//上面三种情况综合考虑,有一种为true,这次匹配就可以判true
//注意,这里也要分前面的b是否匹配以及通配符的情况
if (p[j - 2] == s[i - 1] || p[j - 2] == '.') //x*的x匹配上了,或者是通配符加星号 .* 形式
{
//注意,即使匹配上了,也要看dp[i][j - 2]
//因为对于a和aa*的这种情况,由于字母重复导致误认为要用到后面这组“a*”进行匹配,实际上归到没匹配上的情况里去
dp[i][j] = dp[i][j - 2] || dp[i][j - 1] || dp[i - 1][j];
}
else //证明这次的x*的x没匹配上,可以丢掉
{
dp[i][j] = dp[i][j - 2];
}
}
//3.不匹配情况:普通字母匹配且不相等,直接返回false
//这个一定要摆在最后面!否则*号判断会被这个覆盖
else
{
dp[i][j] = false;
}
}
}
return dp[slength][plength];
}
};
// @lc code=end
时间复杂度和空间复杂度
- 时间复杂度:已知m 和 n 分别是字符串 s 和 p 的长度。因为对每一个s[i]和p[j](0<=i<=m,0<j<=n),都要进行一次计算状态,所以为 O(mn) 。
- 空间复杂度:建立了一个vector型二维数组存放对应位置的状态,所以为 O(mn) 。
反思与总结
比较困难的一道题,着重反思和总结的点就是想明白动态规划怎么做。多看看代码中的注释吧!