这题类似 Regular Expression Matching,但是数据比较强。
首先介绍DP的解法,回忆Regular Expression Matching,我们也用dp(i,j)表示s[0...i-1]和p[0...j-1]是否匹配
基本情况相似,但是更简单:
1. dp(0,0) = true
2. dp(0,j) = dp(0,j-1) && p[j-1] == '*‘
3. dp(i,0) = false
一般情况dp(i,j):
1. dp(i-1,j-1) && (s[i-1] == p[j-1] || p[j-1] == '?')
2. dp(i, j-1) && p[j-1] == '*'
3. dp(i-1, j) && p[j-1] == '*'
由于第i行只用到了第i-1行的数据,所以可以将空间上压缩至一维。我们用一个bool curRow表示当前访问的行号,!curRow表示i-1行
第一个版本
bool isMatch(const char *s, const char *p) {
int m = strlen(s);
int n = strlen(p);
bool curR = 0;
vector<vector<bool>> dp(2);
dp[curR] = (vector<bool>(n+1));
dp[!curR] = (vector<bool>(n+1));
dp[0][0] = true;
for (int i = 1; i <= n; i++)
dp[0][i] = (p[i-1] == '*' && dp[0][i-1]);
const char* tmp = p;
int cnt = 0;
while (*tmp != '\0') if (*(tmp++) != '*') cnt++;
if (cnt > m) return false;
for (int i = 1; i <= m; i++)
{
curR = !curR;
dp[curR][0]=false;
for (int j = 1; j <= n; j++)
{
dp[curR][j] = (dp[!curR][j-1] && (s[i-1] == p[j-1] || p[j-1] == '?') ||
dp[curR][j-1] && p[j-1] == '*' ||
dp[!curR][j] && p[j-1] == '*');
}
}
return dp[curR][n];
}
注意这里有一个剪枝,if (cnt > m) return false,用于迅速将不符合的情况排除。如果没有这一段的话会TLE。
第二个版本
bool isMatch(const char *s, const char *p) {
int m = strlen(s);
int n = strlen(p);
if (!quickElimination(s,p)) return false;
bool curR = 0;
vector<vector<bool>> dp(2);
dp[curR] = (vector<bool>(n+1));
dp[!curR] = (vector<bool>(n+1));
dp[0][0] = true;
for (int i = 1; i <= n; i++)
dp[0][i] = (p[i-1] == '*' && dp[0][i-1]);
for (int i = 1; i <= m; i++)
{
curR = !curR;
dp[curR][0]=false;
for (int j = 1; j <= n; j++)
{
if (p[j-1] == '*')
dp[curR][j] = dp[curR][j-1] || dp[!curR][j];
else if (s[i-1] == p[j-1] || p[j-1] == '?')
dp[curR][j] = dp[!curR][j-1];
else
dp[curR][j] = false;
}
}
return dp[curR][n];
}
bool quickElimination(const char *s, const char *p)
{
int cnt = 0;
while (*p != '\0') if (*(p++) != '*') cnt++;
if (cnt > strlen(s)) return false;
return true;
}
主要的不同是在计算dp(i,j)时用if-else取代一系列的逻辑运算符,速度上提高30%左右。
因为第一个版本在计算时,某个语句失败后会计算以||相连的下一个条件,造成更多运算。
复杂度为O(slen*plen)
直接匹配的方法
在http://yucoding.blogspot.com/2013/02/leetcode-question-123-wildcard-matching.html上有个非常简单优美的方法,比我第一次几十行的代码优秀太多。
bool isMatch(const char *s, const char *p) {
// Start typing your C/C++ solution below
// DO NOT write int main() function
const char* star=NULL;
const char* ss=s;
while (*s){
if ((*p=='?')||(*p==*s)){s++;p++;continue;}
if (*p=='*'){star=p++; ss=s;continue;}
if (star){ p = star+1; s=++ss;continue;}
return false;
}
while (*p=='*'){p++;}
return !*p;
}
主要的思想是:
1.*s==*p or *p == ? :匹配,s++ p++。
2. p=='*':也是一个匹配,但可能有0个或者多个字符用于匹配,所以将'*'所在位置保存(star),并且将s的位置也保存下来(ss)。
3. 如果不匹配,查看之前是否有'*'。没有则返回false,有则对ss的下一个位置和start之后的重新匹配。
暴力法,复杂度是O(slen*slen)。
由于例子中plen和slen相差不大,而且暴力法在搜索途中就能发现不匹配的字符串,终止搜索;而DP需要在遍历完所有子问题后才能给出解,所以第二种方法快得多。
OJ上,暴力法耗时116ms,DP耗时720ms