1. 程式人生 > >[leetcode] Wildcard Matching 萬用字元匹配

[leetcode] 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);
    }
};