首页 > 代码库 > Wildcard Matching[leetcode]直接匹配和DP
Wildcard Matching[leetcode]直接匹配和DP
这题类似 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
Wildcard Matching[leetcode]直接匹配和DP