標籤:支援 包含 完全 學習 ret 過程 string 遇到 結果
給定一個字串 (s) 和一個字元模式 (p) ,實現一個支援 ‘?‘ 和 ‘*‘ 的萬用字元匹配。
‘?‘ 可以匹配任何單個字元。‘*‘ 可以匹配任一字元串(包括Null 字元串)。
兩個字串完全符合才算匹配成功。
說明:
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解釋: 第一個 ‘*‘ 可以匹配Null 字元串, 第二個 ‘*‘ 可以匹配字串 "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]。這樣子的話初始化就很容易了。
在填表的過程中:
- 如果s[i - 1] = p[j - 1],則是字母匹配到了字母,那麼匹配結果取決於前面的匹配結果。
- 如果p[j - 1] = ‘?‘,那麼是問號匹配到了字母,也是匹配結果取決於前面的結果。
- 如果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. 萬用字元匹配