1. 程式人生 > >LeetCode 44. 通配符匹配

LeetCode 44. 通配符匹配

ring 如果 狀態 支持 形式 tag 給定 color ret

給定一個字符串 (s) 和一個字符模式 (p) ,實現一個支持 ‘?‘‘*‘ 的通配符匹配。

‘?‘ 可以匹配任何單個字符。
‘*‘ 可以匹配任意字符串(包括空字符串)。

兩個字符串完全匹配才算匹配成功。

說明:

  • s 可能為空,且只包含從 a-z 的小寫字母。
  • p 可能為空,且只包含從 a-z 的小寫字母,以及字符 ?*

示例 1:

輸入:
s = "aa"
p = "a"
輸出: false
解釋: "a" 無法匹配 "aa" 整個字符串。

示例 2:

輸入:
s = "aa"
p = "*"
輸出: true
解釋: ‘*‘ 可以匹配任意字符串。

示例 3:

輸入:
s = "cb"
p = "?a"
輸出: false
解釋: ‘?‘ 可以匹配 ‘c‘, 但第二個 ‘a‘ 無法匹配 ‘b‘。

示例 4:

輸入:
s = "adceb"
p = "*a*b"
輸出: true
解釋: 第一個 ‘*‘ 可以匹配空字符串, 第二個 ‘*‘ 可以匹配字符串 "dce".

示例 5:

輸入:
s = "acdcb"
p = "a*c?b"
輸入: false

這個題在很多個Tag下,如DP,貪心,回溯等。

動態規劃

對於兩個字符串的問題,容易想到用dp[i][j]這樣的形式來表示狀態。我一開始是定義dp[i][j]為字符串s中前i個字符和p中前j個字符是否匹配(i和j從0開始),由於初始化問題弄了很久,最後寫出的代碼異常垃圾。。。

class Solution {
public:
    
bool isMatch(string s, string p) { if (p.size() == 0 && s.size() == 0) { return true; } if (p.size() == 0) { return false; } else if (p == "*") { return true; } else if (s.size() == 0) { return
false; } int row = p.size(), column = s.size(); vector<vector<int>> dp(row); vector<bool> okDp(row, false); for (int i = 0; i < row; i++) { dp[i].resize(column); } bool matched = false; dp[0][0] = (p[0] == * || p[0] == ? || p[0] == s[0]) ? 1 : 0; if (p[0] == ? || p[0] == s[0]) { matched = true; } for (int i = 1; i < row; i++) { if (p[i] == *) { dp[i][0] = dp[i - 1][0]; if (dp[i][0]) { okDp[i] = true; } } else if (p[i - 1] == * && p[i] == s[0]) { dp[i][0] = matched ? 0 : dp[i - 1][0]; matched = true; } else if (p[i] == ?) { dp[i][0] = matched ? 0 : dp[i - 1][0]; matched = true; } } for (int j = 1; j < column; j++) { if (p[0] == *) { dp[0][j] = 1; } } for (int i = 1; i < row; i++) { for (int j = 1; j < column; j++) { if (isalpha(s[j]) && isalpha(p[i])) { if (s[j] == p[i]) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = 0; } } else if (p[i] == ?) { dp[i][j] = dp[i - 1][j - 1]; } else if (p[i] == *) { if (okDp[i] || dp[i - 1][j]) { okDp[i] = true; dp[i][j] = 1; } } } } return dp[row - 1][column - 1]; } };

後來學習了其他的解法,先上代碼:

class Solution {
public:
    bool isMatch(string s, string p) 
    {
        int n=s.size();
        int m=p.size();
        //代表的是  s[0...i]  p[0..j]是否匹配的情況
        vector<vector<bool>>dp(n+1,vector<bool>(m+1,false));
        dp[0][0]=true;   
        for(int j=1;j<=m;j++)
            dp[0][j]=(p[j-1]==*?dp[0][j-1]:0);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                if(i>0&&(s[i-1]==p[j-1]||p[j-1]==?))
                    dp[i][j]=dp[i-1][j-1];
                else if(p[j-1]==*)
                {
                    dp[i][j]=dp[i-1][j-1]||dp[i-1][j]||dp[i][j-1];         
                }
                else 
                    dp[i][j]=0;
            } 
                
                               
        return dp[n][m];
    }
                               
};

在這裏,狀態dp[i][j]是從1開始計算起來的。一開始不明白為什麽這樣子也行得通,後來考慮到有空串的情況。如""和""對於dp[0][0],""和"*"對於dp[0][1]。這樣子的話初始化就很容易了。

在填表的過程中:

  1. 如果s[i - 1] = p[j - 1],則是字母匹配到了字母,那麽匹配結果取決於前面的匹配結果。
  2. 如果p[j - 1] = ‘?‘,那麽是問號匹配到了字母,也是匹配結果取決於前面的結果。
  3. 如果p[j - 1] = ‘*’,那麽是遇到了星號。星號可能匹配的是空串,那麽就是取決於dp[i][j - 1]的結果。如果匹配了一個字符,那麽就是取決於dp[i - 1][j - 1]。如果匹配了兩個字符,那麽就是取決於dp[i - 2][j - 1]。
    所以,dp[i][j] = dp[i][j - 1] || dp[i - 1][j - 1] || dp[i - 2][j - 1] || ... || dp[0][j - 1]。
    又因為dp[i - 1][j] = dp[i - 1][j - 1] || dp[i - 2][j - 1] || ... || dp[0][j - 1]。
    所以dp[i][j] = dp[i][j - 1] || dp[i - 1][j]。

LeetCode 44. 通配符匹配