首页 > 代码库 > 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