leetcode 44.通配符匹配
原题链接
题目
给定一个字符串 s 和一个字符模式 p,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。
‘?’ 可以匹配任何单个字符。
‘*’ 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。
说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。
示例 1:
输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。
示例 2:
输入:
s = “aa”
p = ""
输出: true
解释: '’ 可以匹配任意字符串。
示例 3:
输入:
s = “cb”
p = “?a”
输出: false
解释: ‘?’ 可以匹配 ‘c’, 但第二个 ‘a’ 无法匹配 ‘b’。
示例 4:
输入:
s = “adceb”
p = “ab”
输出: true
解释: 第一个 ‘’ 可以匹配空字符串, 第二个 '’ 可以匹配字符串 “dce”.
示例 5:
输入:
s = “acdcb”
p = “a*c?b”
输入: false
解题过程
1.最开始的解题思路。
因为这题的通配符匹配和leetcode.10 正则表达式匹配 很像,所以最开始想用类似的思路解决。
bool isMatchCore(const string& s,const string& p,int i1,int i2){
//i1到达尾判断i2是否到达尾或者,返回true否则false
//为什么不是i1到达尾:因为i1到尾i2没到也可以i2及其以后全部为*表示空
if(i1==len1){
while(i2<len2 && p[i2]=='*') i2++;
return i2==len2;
}
//当前pattern字符串的当前字符为'*'
if(i2<len2&& p[i2]=='*'){
return
//i2不动,i1前进1格(匹配多个字符情况
isMatchCore(s,p,i1+1,i2)
//i1不动,i2前进一格说明匹配空字符
|| isMatchCore(s,p,i1,i2+1);
}
//当前p不为*,且
//在i1非尾的情况下,当前字符一样或者p字符为?,各前进一格
if(i2<len2 && (s[i1]==p[i2]||p[i2]=='?')){
return isMatchCore(s,p,i1+1,i2+1);
}
return false;
}
在源函数 isMatch 中我们只需要调用isMatchCore函数且索引i1、i2设置为0即可。
bool isMatch(string s, string p)
结果是超时:
2.改进思路1中的解法
事实上我们可以发现,由于’ * '号的增加,递归栈也在不停地调用,深度过大耗时也会过大。不难发现 ’ * ‘号连续时,无论有多少个都相当于1个,所以在源函数中可进行处理,将连续的’ * '号进行删除只留一个。在源函数isMatch中可以进行改进:
bool isMatch(string s, string p) {
len1=s.size();
len2=p.size();
//两空为true
if(len1==0&&len2==0) return true;
//2个以上连着的*号其实都只算一个*号
int i2=0;
while(i2<p.size()){
//若当前非*,找到第一个*
int cur_len=p.size();
while(i2<cur_len && p[i2]!='*'){
i2++;
}
if(i2<cur_len &&p[i2]=='*' ){
int start=i2;
while(i2<cur_len&& p[i2]=='*'){
i2++;
}
//清除多余*
p.erase(start,i2-start-1);
//重置位置
i2=start+1;
}
}
len2= p.size();
return isMatchCore(s,p,0,0);
}
结果是:仍然超时。
3.解决方法
经过之前两次的超时说明这个模仿leetcode.10
正则表达式匹配的回溯法难以通过。 用动态规划试试。
创建一个二维向量dp[m+1][n+1],其中m、n为s、p的长度,dp[i][j]表示s、p的前i、j个字符是否能匹配。
状态转移方程有:
1、当p[ j-1 ] == ‘?'或s[ i-1 ] == p[ j-1 ]时,dp[ i ] [ j ] = dp[ i-1 ] [ j-1 ]即完全由前i-1个s和前j-1个p决定当前是否能匹配。
2、当p[ j-1 ]==’ * ‘时,dp[ i ] [ j ]= dp[ i-1 ] [ j ] || dp[ i ] [ j-1 ]。即当前能否匹配取决于当前位置(‘ * ’为空)dp[ i ] [ j-1 ]的状态;或者dp[ i-1 ] [ j ]即不计当前s字符情况下,前j个p的字符串能否匹配,‘* ‘ 的作用即在前一个i-1、j状态下多充当一个字符s[i](‘ * ’非空)。
bool isMatch(string s, string p) {
int m=s.size();
int n=p.size();
//dp数组,dp[i][j]表示s的前i个和p的前j个是否匹配,默认为false
vector<vector<bool>> dp(m+1,vector<bool>(n+1,false));
//初始化:(第一列全为false,表明当p为空不管s多少个都为空(除了均空的dp[0][0]
dp[0][0]=true;
for(int i=1;i<=n;++i){
//当s为空,p只要前面匹配成功且当前为*,则也为成功匹配
dp[0][i]=dp[0][i-1] && p[i-1]=='*';
}
for(int i_s=1;i_s<=m;i_s++){
for(int i_p=1;i_p<=n;i_p++){
//如果当前俩字符相同或p字符为'?'
if(s[i_s-1]==p[i_p-1] || p[i_p-1]=='?'){
dp[i_s][i_p]=dp[i_s-1][i_p-1];
}
//当前p字符为'*'
else if(p[i_p-1]=='*'){
dp[i_s][i_p]= dp[i_s-1][i_p] || dp[i_s][i_p-1];
}
}
}
return dp[m][n];
}
4.感想
本题的’ * ‘和之前的正则表达式星号不是一个量级,能匹配0-无穷多个任意字符,相比正则表达式的’ * '只能复制其前一个的若干个,少了很多限制,让递归深度过大,导致超时。
但是动态规划是不会出现这种情况的。