题目链接https://leetcode.com/problems/wildcard-matching/
同时在lintcode上也有http://www.lintcode.com/zh-cn/problem/wildcard-matching/
也是《剑指offer》中的题目
问题描述:
判断两个可能包含通配符“?”和“*”的字符串是否匹配。匹配规则如下:
'?' 可以匹配任何单个字符。 '*' 可以匹配任意字符串(包括空字符串)。 两个串完全匹配才算匹配成功。
接口如下:其中s为待匹配串(不含'?'和'*'),p为模式串(含'?'和'*')bool isMatch(const char *s, const char *p)
一些例子:
isMatch("aa", "*") → true isMatch("aa", "a*") → true isMatch("ab", "?*") → true isMatch("aab", "c*a*b") → false
(一)看到题目我最先想到的是dfs暴力枚举所有情况,想想也知道会TLE,不出所料。感觉自己真是low爆了!
(二)接着往下想,会考虑到使用动态规划,想想字符串的编辑距离问题,那么这个题目的动态转移方程应该也是类似推导出来的。不妨令dp[i][j]表示s(0,i)的子串是否与p(0,j)的子串完全匹配,那么我们来看看dp[i][j]与dp[i-1][j], dp[i][j-1], dp[i-1][j-1]的关系:
例如:s = "aab", p = "a?*b", 那么可得出如下表格:
可以发现dp[i][j]为真有三种情况:(1) dp[i-1][j-1]为真,且s[i] 与 s[j]匹配;(2) dp[i-1][j]为真,且p[j]为'*',此时用'*'匹配多个字符;(3) dp[i][j-1]为真,且p[j]为'*',此时'*'匹配空字符。
故有:dp[i][j] = ((dp[i-1][j-1] && equal(s+i,p+j)) || (dp[i-1][j] && (*(p+j)=='*')) || (dp[i][j-1] && (*(p+j)=='*')));
此处为了方便,我将dp[0][0~n]与dp[0~m][0]初始化为空字符串与p的匹配情况和s与空字符串的匹配情况,具体代码如下:
class Solution {
public:
/**
* @param s: A string
* @param p: A string includes "?" and "*"
* @return: A boolean
*/
bool equal(const char *s, const char *p){
if(*s == *p) return true;
if(*p == '?' || *p == '*') return true;
return false;
}
bool isMatch(const char *s, const char *p) {
// write your code here
if(s == NULL && p == NULL) return true;
int m = strlen(s);
int n = strlen(p);
if(m == 0 && n == 0) return true;
vector<bool> tmp(n+1,false);
vector<vector<bool> > dp(m+1,tmp);
dp[0][0] = true;
for(int i=0; i<n; ++i){
dp[0][i+1] = (dp[0][i] && (*(p+i) == '*'));
}
for(int i=0; i<m; ++i){
for(int j=0; j<n; ++j){
dp[i+1][j+1] = ((dp[i][j] && equal(s+i,p+j)) || (dp[i][j+1] && (*(p+j)=='*')) || (dp[i+1][j] && (*(p+j)=='*')));
//printf("dp[%d][%d]=%d ",i+1,j+1,dp[i+1][j+1]?1:0);
}
//printf("\n");
}
return dp[m][n];
}
};
(三)后来看了discuss才发现还可以使用 贪心算法,不仅时间上有优化,还将空间压缩至O(1)。
主要思想是:从两个字符串的起始位置开始匹配,遇到通配符 '*' 时,优先考虑让其匹配空字符,
但记录该 ’*‘ 的位置,以及对应的s指针的位置,然后继续往后匹配;直到无法继续,则返回上一个 ’*‘ 的位置,考虑让其匹配1个字符,然后继续往后匹配;直到无法匹配,则重新返回上一个 ’*‘ 的位置,考虑让其匹配2个字符。。。。。。直到s的指针到达终点。此时,若当前p指针后面均为’*‘,则返回true,否则返回false。
代码如下:
class Solution {
public:
/**
* @param s: A string
* @param p: A string includes "?" and "*"
* @return: A boolean
*/
// greedy method
bool isMatch(const char *s, const char *p) {
// write your code here
if(s == NULL && p == NULL) return true;
int m = strlen(s);
int n = strlen(p);
if(m == 0 && n == 0) return true;
int si = 0, pi = 0;
int xidx = -1, mtch = -1;
while(si < m){
if(pi < n && (*(p+pi)=='*')){
xidx = pi++;
mtch = si; // si对应xidx的位置
}else if(pi < n && (*(s+si) == *(p+pi) || *(p+pi) == '?')){
++ si;
++ pi;
}else if(xidx > -1){ // 上一个 '*' 的位置
pi = xidx + 1;
si = ++ mtch;
}else{
return false;
}
}
while(pi < n && (*(p+pi) == '*')) ++ pi;
return (pi == n);
}
};