题目:
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
思路:
拿到这样的题目描述,不难想到动态规划。不过对于这道题目,动态规划的运行效率反而不是最优的,主要是因为没有利用到’*'的威力。具体看下面三种思路的分析及比较。
1、动态规划:定义dp[i][j]为一个布尔变量,代表s的前i个字符是否可以和p的前j个字符完全匹配成功。则递推方程如下:
1)如果p[j] == '*',则该‘*'即可以出现0次,也可以出现至少1次,即dp[i+1][j+1] = dp[i+1][j] || dp[i][j+1];
2)如果p[j] != '*',则完全可以匹配的条件是:s和t的当前最后一个字符可以匹配,s和t除去当前最后字符之外,其余前面的字符也可以完全匹配。
2、双线性扫描:考虑到‘*’可以匹配任意多个字符(也就是说,一旦出现‘*’,则此时所有s中已经扫描到的位置都可以被匹配),那么我们可以记录并不断更新每个‘*’至少需要匹配的字符长度。具体做法是:当出现一个‘*’时,则记录当前s字符串已经被扫描到的位置,再接着试图匹配s和t后面的字符位置。一旦发现后面位置无法匹配,则返回到这里,将不匹配的字符用当前最后一个‘*’来匹配(也就是说,‘*’至少需要匹配的字符长度一旦在后面无法匹配时会被迫递增)。最后再判断p的指针到最后都是‘*’,如果是,则可以匹配,否则就不可以匹配。(此解法参考了小榕流光的博客:http://blog.csdn.net/qq508618087/article/details/51044029)。
3、深度优先搜索:本质上该方法还是和双线性扫描的思路一致,只是用了深搜的实现方式。在遇到一个‘*’的时候,我们还是试图去寻找该‘*’至少需要匹配的字符串长度,以便于为后面尝试可能的匹配留下最大的搜索空间。此时将dfs的返回值定义为匹配的三种不同状态(见下面代码中的注释),以便于处理平凡情况以及有效剪枝。
实现发现,思路2和3的运行效率要远远高于思路1。
代码:
1、动态规划:
class Solution {
public:
bool isMatch(string s, string p)
{
int n1 = s.length(), n2 = p.length();
vector<vector<bool>> dp(n1+1, vector<bool>(n2+1, false));
dp[0][0] = true;
for(int j = 0; j < n2; ++j)
{
if(p[j] == '*')
dp[0][j+1] = dp[0][j];
}
for(int i = 0; i < n1; ++i)
{
for(int j = 0; j < n2; ++j)
{
if(p[j] == '*')
// dp[i+1][j] means p[j] matches 0 time
// dp[i][j+1] means p[j] matches at least 1 time
dp[i+1][j+1] = dp[i+1][j] || dp[i][j+1];
else
// dp[i][j] means the whether the previous can be matched
// (s[i] == p[j] || p[j] == '?') means whether the last charchater can be matched
dp[i+1][j+1] = dp[i][j] && (s[i] == p[j] || p[j] == '?');
}
}
return dp[n1][n2];
}
};
2、双线性扫描:
class Solution {
public:
bool isMatch(string s, string p)
{
int preS = -1, preP = -1, i = 0, j = 0;
int len1 = s.size(), len2 = p.size();
while(i < len1)
{
if(s[i] == p[j] || p[j] == '?') // matched
i++, j++;
else if(p[j] == '*') // at least we can match s[0, i]
preS = i + 1, preP = j++;
else if(preP == -1) // no previous matched case exists
return false;
else // re-match from here
i = preS, j = preP;
}
while(p[j]=='*') // skip the more than necessary '*'
j++;
return i == len1 && j == len2;
}
};
3、深度优先搜索:
class Solution {
public:
bool isMatch(string s, string p)
{
return dfs(s, p, 0, 0) == 2;
}
private:
int dfs(string& s, string& p, int si, int pi)
{
if (si == s.size() and pi == p.size()) return 2; // matched
if (si == s.size() and p[pi] != '*') return 0; // unmatched: reached the end of s
if (pi == p.size()) return 1; // unmatched: not reached the end of s
if (p[pi] == '?' or s[si] == p[pi])
return dfs(s, p, si+1, pi+1);
if (p[pi] == '*') { // calculate the shortest length p[pi] need to cover
if (pi+1 < p.size() and p[pi+1] == '*')
return dfs(s, p, si, pi+1); // skip duplicate '*'
for(int i = 0; i <= s.size()-si; ++i)
{
int ret = dfs(s, p, si+i, pi+1);
if (ret == 0 or ret == 2) // reached the end of s, so return immediately
return ret;
}
}
return 1;
}
};