目录
一.通配符匹配问题⭐⭐⭐⭐
1.对应letecode链接:
2.题目描述:
3.解题思路:
本题的题意是实现支持'?' 和 '*' 的通配符匹配的字符串匹配,像这种字符串匹配的题目大概率是这个样本对应模型:在这里我们可以进行尝试,定义这个递归函数进行尝试:
bool process(const string&s,int si,const string&p,int pi);
这个递归函数的含义是字符串s从si位置一直到结尾能和字符串p从pi位置开始一直到字符串的结尾。返回值:如果能够匹配上则返回true否则返回false.
下面我们来分析可能性:
1.当前si位置上的字符和pi位置的字符相等:此时能否匹配成功决定于后面的字符能否匹配成功
2.情况二:si位置上的字符不等于pi位置上的字符但是pi位置上的字符为 ?那么此时字符串能否匹配成功也是决定与后面的字符能否匹配成功这是因为.可以匹配任意单个字符
情况三:也是最复杂的一种情况:si位置上的字符和 pi位置的字符不相等并且pi位置的字符为*
此时这个*可以匹配s字符串当中的0个字符,1个字符,2个字符,3个字符,4字符,一直匹配到字符串的结尾,在这个过程当中只要有一次匹配成功了那么就匹配成功了。情况四非常的简单就是si位置的字符和pi位置的字符不相等并且pi位置的字符不是*也不是.此时直接返回false即可
4.对应代码:
class Solution { public: bool isMatch(string s, string p) { return process(s, 0, p, 0); } //字符串s从0位置开始一直到结尾能否匹配字符串p从pi位置开始一直到结尾 bool process( string& s, int si, string& p, int pi) { //两个字符串都到了字符串的结尾空串和空串一定匹配 if (si == s.size() && pi == p.size()) { return true; } //s还没结束但是p已经结束了 if (pi == p.size()) { return false; } if (si == s.size()) { //*可以匹配空串 return (p[pi] == '*') && process(s, si, p, pi + 1); } //情况一和情况二合并 if (s[si] == p[pi] || p[pi] == '?') { return process(s, si + 1, p, pi + 1); } //情况三 else if (p[pi] == '*') { //开始进行产生 for (int i = 0; i <= s.size() - si; i++) { //开始进行尝试匹配 if (process(s, si + i, p, pi + 1)) { return true; } } } //情况四: return false; } };
对应记忆化搜索:
class Solution { public: vector<vector<int>>dp; bool isMatch(string s, string p) { dp.resize(s.size() + 1, vector<int>(p.size() + 1, -1)); return process(s, 0, p, 0); } bool process(const string& s, int si, const string& p, int pi) { if (si == s.size() && pi == p.size()) { return true; } if (si == s.size()) { return p[pi] == '*' && process(s, si, p, pi + 1); } if (pi == p.size()) { return false; } if (dp[si][pi] != -1) { return dp[si][pi]; } bool ans = false; if (s[si] == p[pi] || p[pi] == '?') { ans = process(s, si + 1, p, pi + 1); } else if (p[pi] == '*') { for (int i = 0; i <= s.size() - si; i++) { if (process(s, si + i, p, pi + 1)) { ans = true; } } } dp[si][pi] = ans; return ans; } };
斜率优化我们可以观察一下当p[pi]=='*时的对应关系:
dp[i][j]=dp[i][j+1] | dp[i+1][j+1] | dp[i+2][j+1] | dp[i+3][j+1]............................
我们来看一下dp[i+1][j]=dp[i+2][j+1] | dp[i+3][j+1] | dp[i+4][j+1]...........................
通过观察可以发现dp[i+1][j]可以优化dp[i][j]
对应代码:
class Solution { public: bool isMatch(string s, string p) { vector<vector<bool>>dp(s.size()+1,vector<bool>(p.size()+1)); int N=s.size()+1; int M=p.size()+1; dp[N-1][M-1]=true; for(int i=p.size()-1;i>=0;i--)//根据暴力递归得到 { dp[s.size()][i]=p[i]=='*'&&dp[s.size()][i+1]; } for(int i=s.size()-1;i>=0;i--) { for(int j=p.size();j>=0;j--) { if(s[i]==p[j])//全部根据暴力递归得到的状态转移方程 { dp[i][j]=dp[i+1][j+1]; } else { if(p[j]=='?') { dp[i][j]=dp[i+1][j+1]; } else if(p[j]=='*') { dp[i][j]=dp[i][j+1]|dp[i+1][j]; } else { dp[i][j]=false; } } } } return dp[0][0]; } //递归含义str1从[0.......i],str2[0......j]能否匹配 bool process(const string&str1,const string&str2,int i,int j) { //str2可能有*和? if(i==str1.size()&&j==str2.size()) { return true; } if(i==str1.size()) { return str2[j]=='*'&&process(str1,str2,i,j+1); } if(j==str2.size())//str2为空串必定为false; { return false; } //来到普遍位置 if(str1[i]==str2[j]) { return process(str1,str2,i+1,j+1); } //说明不相等 if(str2[j]=='?') { return process(str1,str2,i+1,j+1); } if(str2[j]=='*') { /*for(int k=0;k<=str1.size()-i;k++)//可以匹配一个也可以匹配很多个 { if(process(str1,str2,i+k,j+1)) { return true; } } */ return process(str1,str2,i,j+1)|process(str1,str2,i+1,j); } //说明是普通字符并且不相等 return false; } };
二.正则表达式匹配⭐⭐⭐⭐⭐
1.对应letecode链接:
2.题目描述:
3.解题思路:
本题相比上一题而言难度比较大.同样的这种字符串匹配的题一般是这个样本对应模型而样本对应模型的如何考虑都是有套路的:
我们一般可以考虑字符串结尾位置的字符如何如何。在这里我们可以进行尝试,下面给出递归函数的定义:
//递归含义str取前si个字符能否匹配上pattern取前pi个字符。 //能够匹配返回true不能够匹配返回false bool process(const string&str,int si,const string&pattern,int pi)
下面我们来分析可能性:
情况一:s[i-1]与p[j-1]是匹配的最右端的字符是匹配的,那么,大问题的答案 = 剩余子串是否匹配。
情况二:s[i-1]与p[j-1]是匹配的最右端的字符是匹配的但是pattern[j-1]位置上面的字符是*,此时又有两种情况了:
- s[i-1]位置的字符和p[j-2]位置上面的字符能够匹配上
- s[i-1]位置的字符和p[j-2]位置上的字符不能匹配上
p[j-1]是星号,并且 s[i-1]和p[j−2] 匹配,要考虑三种情况:,p[j−1] 星号可以让p[j−2] 在 p 串中消失、出现 1 次、出现 >=2 次。
小情况二很简单了:既然不能匹配上那不好意思我只能拉着你一起变成空串了
情况三:真的匹配不是此时直接返回false即可
4.对应代码:
class Solution { public: bool isMatch(string s, string p) { return process(s, s.size(), p, p.size()); } //递归含义str取前si个字符能否匹配上pattern取前pi个字符。 //能够匹配返回true不能够匹配返回false bool process(const string& str, int si, const string& pattern, int pi) { //两个同时是这个空串了一定匹配 if (si == 0 && pi == 0) { return true; } //字符串str是空串了但是pattern不是空串 if (si == 0) { //此时pattern[si-1]=='*只能将他前面的那个字符给消除掉 return pattern[pi - 1] == '*' && process(str, si, pattern, pi - 2); } //字符串pattern是空串了但是str串还不是空串 if (pi == 0) { return false; } //字符串si-1位置的字符和pi-1位置的字符能够匹配 if (str[si - 1] == pattern[pi - 1] || pattern[pi - 1] == '.') { return process(str, si - 1, pattern, pi - 1); } //当前位置可能是*而导致的不能够匹配 else if (pattern[pi - 1] == '*') { //当前位置是*分为两种情况: //1.pi-2位置的字符能够匹配上si-1位置上的字符 //2.pi-2位置的字符和si-1位置上的字符匹配不了不好意思此时只能用pi-1位置上的*和pi-2位置上 //的字符一起变成空 //情况一: if (pattern[pi - 2] == str[si - 1] || pattern[pi - 2] == '.') { //此时能够匹配上但是要匹配几个了? //匹配0个,匹配1个,匹配>=2个 return process(str, si, pattern, pi - 2) || process(str, si - 1, pattern, pi - 2) || process(str, si - 1, pattern, pi); } else { //情况二: return process(str, si, pattern, pi - 2); } } return false;//当前si-1位置的字符和pi-1位置的字符真的不匹配 } };
对应严格位置的动态规划:
class Solution { public: bool isMatch(string s, string p) { int M = s.size(); int N = p.size(); vector<vector<bool>>dp(M + 1, vector<bool>(N + 1)); //默认初始化为false dp[0][0] = true; for (int i = 1; i <= N; i++) { if (p[i - 1] == '*') { dp[0][i] = dp[0][i - 2]; } } //来到普遍位置 for (int i = 1; i <= M; i++) { for (int j = 1; j <= N; j++) { if (s[i - 1] == p[j - 1] || p[j - 1] == '.') { dp[i][j] = dp[i - 1][j - 1]; } else if (p[j - 1] == '*') { if (p[j - 2] == s[i - 1] || p[j - 2] == '.') { dp[i][j] = dp[i][j - 2] || dp[i - 1][j - 2] || dp[i - 1][j]; } else { dp[i][j] = dp[i][j - 2]; } } else { //可以省略 dp[i][j] = false; } } } return dp[M][N]; } //递归含义str取前si个字符能否匹配上pattern取前pi个字符。 //能够匹配返回true不能够匹配返回false bool process(const string& str, int si, const string& pattern, int pi) { //两个同时是这个空串了一定匹配 if (si == 0 && pi == 0) { return true; } //字符串str是空串了但是pattern不是空串 if (si == 0) { //此时pattern[si-1]=='*只能将他前面的那个字符给消除掉 return pattern[pi - 1] == '*' && process(str, si, pattern, pi - 2); } //字符串pattern是空串了但是str串还不是空串 if (pi == 0) { return false; } //字符串si-1位置的字符和pi-1位置的字符能够匹配 if (str[si - 1] == pattern[pi - 1] || pattern[pi - 1] == '.') { return process(str, si - 1, pattern, pi - 1); } //当前位置可能是*而导致的不能够匹配 else if (pattern[pi - 1] == '*') { //当前位置是*分为两种情况: //1.pi-2位置的字符能够匹配上si-1位置上的字符 //2.pi-2位置的字符和si-1位置上的字符匹配不了不好意思此时只能用pi-1位置上的*和pi-2位置上 //的字符一起变成空 //情况一: if (pattern[pi - 2] == str[si - 1] || pattern[pi - 2] == '.') { //此时能够匹配上但是要匹配几个了? //匹配0个,匹配1个,匹配>=2个 return process(str, si, pattern, pi - 2) || process(str, si - 1, pattern, pi - 2) || process(str, si - 1, pattern, pi); } else { //情况二: return process(str, si, pattern, pi - 2); } } return false;//当前si-1位置的字符和pi-1位置的字符真的不匹配 } };
总结:
类似于两个字符串的这种问题一般是样本对应模型即一个做行一个做列,通常我们思考的点是每个位置结尾如何如何。