leetcode 10 正则表达式匹配(c++)
给你一个字符串 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 = "c*a*b" 输出:true 解释:因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入:s = "mississippi" p = "mis*is*p*." 输出:false
### 思路
直接考虑所有的情况然后递归,先是判断是否匹配结束,匹配结束的标准就是字符串s和字符串p同时用完,所以只需要判断是否同时有j==p.size()和i==s.size(),其中i、j代表s和p中匹配到的字符的下标,接下来就判断匹配过程中可能遇到的两种情况,(**这种情况其实没有必要讨论,因为这种情况其实和一个*没有区别,*代表的是有任意个,所以任意个也可以是0个或一个,没有意义)。
第一种情况:
s[i]==p[j],如果存在p[j+1]并且p[j+1]!='*',这种情况下,直接i++、j++之后继续往下比较就可以了,p[j]可以是直接等于s[i]或者等于'.',对应程序上就写为
if(i<s.size()) return (s[i]==p[j]||p[j]=='.')&&match(s,p,++i,++j)
第二种情况:
不需要s[i]==p[j]或者p[j]=='.',但是p[j+1]存在而且p[j+1]=='*',这个时候首先认为*代表的是前面的字符个数为0,这样直接将p[j]以及p[j+1]跳过,另外一种,不认为*代表将前面的字符的个数设置为0,那么这种时候就需要(s[i]==p[j]||p[j]=='.')为真,才可以让p[j]进入到比对的序列里面去,比对之后,因为不清楚具体的*代表的个数,所以不跳过p[j]以及p[j+1],让它继续和s[i+1]进行比对,因为如果后续比对不上,是可以通过第二种的第一种情况直接将其跳过,所以具体的程序逻辑就是:
return match(s,p,i,j+2)||(i!=s.size()&&(s[i]==p[j]||p[j]=='.')&&match(s,p,++i,j));
这样不断的进行递归,最后检查出是否满足匹配条件,但是效率很低,存在很多重复子问题。
class Solution { public: bool match(string &s,string &p,int i,int j) { if(j==p.size())return i==s.size(); if(p.size()-j>1&&p[j+1]=='*') { return match(s,p,i,j+2)||(i!=s.size()&&(s[i]==p[j]||p[j]=='.')&&match(s,p,++i,j)); } else { return (i!=s.size()&&(s[i]==p[j]||p[j]=='.')&&match(s,p,++i,++j));//开始写成i++了,超出边界了,尴尬了,低级错误 } } bool isMatch(string s, string p) { return match(s,p,0,0); } };
### 思路
动态规划首先就是建立一个数组(维数视问题复杂度而定,本题建立一个二维的数组),其中dp[i][j]代表长度为i的s字符串的子串,能否用长度为j的p字符串的子串进行匹配(子串是从字符串首部开始的),可以匹配结果为1,不能为0。
首先s和p长度为0的时候肯定可以匹配,那么先将dp[0][0]置为1,然后开始循环,s的子串长度从0开始(注意一定要从0开始,因为p的开头可能会存在a*这种子串,这是可以在不需要的时候直接忽略掉的,s的子串长度从0开始循环,就可以考虑到忽略它们的情况),直到长度为s的长度时结束,p的长度是从1开始的,首先因为dp[0][0]置为1了,已经考虑到了两个空子串的情况,其次dp[i][j]的情况和dp[i][j-1]有关,所以如果从0开始会经常溢出,并且如果s的子串有长度,而p这边子串为空,那么肯定是匹配不上的,所以就是设置的初始值0. 。
然后看动态转移方程,其实更新的方法和第一种方法是一样的,只是用二维数组存储了各种情况下的结果,不用重复计算,节约了很多时间,可以看下面的代码就可以看出来:
首先当j>1时,代表有两个以及以上的元素,如果p[j-1]=='*',代表现在的p子串的结尾元素是*,那么如果说s的子串长度大于0,代表这个子串中是有元素的,那么dp[i][j]=dp[i-1][j]&&(s[i-1]==p[j-2]||p[j-2]=='.');如果说这边算出来的dp[i][j]为0,那么其实还可以直接通过这个*忽略掉*前面字符,所以还可以有dp[i][j]=dp[i][j]||dp[i][j-2];
第二种,最后结尾的字符不等于'*',那么就是中规中矩的计算是否有dp[i][j]=dp[i-1][j-1]&&(p[j-1]==s[i-1]||p[j-1]=='.'); 从两个状态转移方程可以明显看出,逻辑和第一种方法是一样的,所以动态规划其实就是找好状态转移方程以及建立好一个数组存储子问题的结果就可以了。
class Solution{ public: bool isMatch(string s, string p) { int l1=s.size(); int l2=p.size(); vector<vector<int>>dp(l1+1,vector<int>(l2+1,0)); dp[0][0]=1; for(int i=0;i<=l1;++i) { for(int j=1;j<=l2;++j) { if(j>1&&p[j-1]=='*') { if(i>0) dp[i][j]=dp[i-1][j]&&(s[i-1]==p[j-2]||p[j-2]=='.'); dp[i][j]=dp[i][j]||dp[i][j-2]; } else if(i>0) { dp[i][j]=dp[i-1][j-1]&&(p[j-1]==s[i-1]||p[j-1]=='.'); } } } return dp[l1][l2]; } };
From : https://zhuanlan.zhihu.com/p/104115952
博主思路详细,多学习思考!