Implement wildcard pattern matching with support for '?'
and '*'
.
'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).
The matching should cover the entire input string (not partial).
The function prototype should be:
bool isMatch(const char *s, const char *p)
Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c*a*b") → false
这道题是带通配符的模式匹配,题目难度为Hard。
直观的想法是采用回溯法或动态规划来解决题目,试了下回溯法超时了,这里就不列出代码了。用match[i][j]表示s[0~i-1]能否和p[0~j-1]匹配,如果p[j-1]为'*',由于'*'可以匹配任意长度(包含0),若匹配长度为0,则match[i][j] = match[i][j-1],若匹配长度不为0,则match[i][j] = match[i-1][j],这里用匹配长度1表示是因为可以逐个递推下去;如果p[j-1]为'?'或s[i-1]==p[j-1],则match[i][j] = match[i-1][j-1]。这样通过动态规划的方法即可得出最终能否匹配,具体代码:
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
vector<vector<bool>> match(m+1, vector<bool>(n+1, false));
match[0][0] = true;
for(int i=1; i<=m; ++i) match[i][0] = false;
for(int i=1; i<=n; ++i) match[0][i] = match[0][i-1] && (p[i-1]=='*');
for(int i=1; i<=m; ++i) {
for(int j=1; j<=n; ++j) {
if(p[j-1] == '*')
match[i][j] = match[i-1][j] || match[i][j-1];
else if(p[j-1] == '?' || s[i-1] == p[j-1])
match[i][j] = match[i-1][j-1];
else
match[i][j] = false;
}
}
return match[m][n];
}
};
虽然通过了测试,不过耗时非常长,没有想到更高效的方法。看了别人的代码,效率高的方法依然采用回溯法来解决题目,只是在回溯过程中进行了优化。匹配过程中记录下'*'出现的位置,在后续不能继续匹配时回溯到此位置让'*'匹配更多的字符来继续后续匹配。具体代码:class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
int i = 0, j = 0, sPos = -1, pPos = -1;
while(i < m) {
if(j < n && (s[i] == p[j] || p[j] == '?')) {
++i;
++j;
}
else if(j < n && p[j] == '*') {
sPos = i;
pPos = ++j;
}
else if(pPos >= 0) {
i = ++sPos;
j = pPos;
}
else return false;
}
while(j < n && p[j] == '*') ++j;
return j == n;
}
};
这里回溯只进行了一层,为何能去掉之前'*'位置的回溯呢?这也成为能否写出以上代码的关键。按照上述方法,假如在模式中匹配到两个'*'后出现了不匹配,第二个'*'之前部分记为p[0~j],0~j之间还有一个'*',p[0~j]匹配的字符串记为s[0~i],此时s[0~i]是p[0~j]能够匹配的最短字符串,回溯到第二个'*'位置时让'*'匹配的字符加1,即从s[i+1]继续匹配。如果回溯到第一个'*'位置,再次匹配到p[0~j]时字符串为s[0~k],则k>i,因为回溯时至少让第一个'*'匹配的字符加1。然后从s[k+1]继续匹配,而k>i,所以所有这些情况在回溯到第二个'*'时都覆盖到了,因此在回溯时只需要回溯到最近的'*'位置即可,通过这种策略极大地减少了回溯分支,效率得到了很大提升。