44. Wildcard Matching
相关问题:10. Regular Expression Matching
问题:
正则表达中,给定匹配对象s,和模式串p,问是否匹配。
其中,* 可匹配任意长度字串,? 可匹配一个单位长度的字串。
Example 1: Input: s = "aa", p = "a" Output: false Explanation: "a" does not match the entire string "aa". Example 2: Input: s = "aa", p = "a*" Output: true Explanation: '*' means zero or more of the preceding element, 'a'. Therefore, by repeating 'a' once, it becomes "aa". Example 3: Input: s = "ab", p = ".*" Output: true Explanation: ".*" means "zero or more (*) of any character (.)". Example 4: Input: s = "aab", p = "c*a*b" Output: true Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore, it matches "aab". Example 5: Input: s = "mississippi", p = "mis*is*p*." Output: false Constraints: 0 <= s.length <= 20 0 <= p.length <= 30 s contains only lowercase English letters. p contains only lowercase English letters, '.', and '*'. It is guaranteed for each appearance of the character '*', there will be a previous valid character to match.
解法:DP(动态规划)Backtracking(回溯算法)
一,回溯算法:(不推荐)
对于本问题,两个变量:
- 路径:到目前为止匹配到的位置s[sp], p[pp]
- 选择列表:下一次匹配的可能:
- 字母相同 or 模式串匹配到? or 模式串匹配到*:sp+1,pp+1
- 模式串匹配到*:sp,pp+1 或者 sp+1,pp
处理过程:
- base:递归退出条件:
- sp和pp都匹配到最后一位。res=true,匹配成功
- 只有pp匹配到最后一位,匹配失败
- 只有sp匹配到最后一位,若pp只剩下*,那么匹配成功,否则匹配失败
- 做选择:对于当前位置,选择其中一个可用数字a。
- sp+1, pp+1 或者 sp不变,pp+1 或者 sp+1,pp不变
- 撤销选择:回退到选择数字a之前的状况。
- sp,pp
⚠️ 注意:本题使用回溯还是会超时,即使使用 preDeleteMultX 将连续多个*转为一个*
代码参考:
1 class Solution { 2 public: 3 void backtrack(bool& res, string s, string p, int sp, int pp) { 4 if(sp == s.size() && pp == p.size()) { 5 res = true; 6 return; 7 } else if(pp == p.size()) { 8 return; 9 } else if(sp == s.size()) { 10 if(p[pp] == '*') { 11 backtrack(res, s, p, sp, pp+1); 12 if(res) return; 13 } 14 return; 15 } 16 if(s[sp] == p[pp] || p[pp] == '?' || p[pp] == '*') { 17 backtrack(res, s, p, sp+1, pp+1); 18 } 19 if(res) return; 20 if(p[pp] == '*') { 21 backtrack(res, s, p, sp, pp+1); 22 if(res) return; 23 backtrack(res, s, p, sp+1, pp); 24 if(res) return; 25 } 26 return; 27 } 28 void preDeleteMultX(string& p) { 29 int pos = 0; 30 while(pos<p.size()) { 31 if(p[pos]=='*') { 32 pos++; 33 while(pos<p.size() && p[pos]=='*') { 34 p.erase(p.begin()+pos); 35 } 36 }else{ 37 pos++; 38 } 39 } 40 } 41 bool isMatch(string s, string p) { 42 bool res = false; 43 preDeleteMultX(p); 44 backtrack(res, s, p, 0, 0); 45 return res; 46 } 47 };
二,DP
1.确定【状态】:
- 字符串s的第i个字符:s[i]
- 匹配串第j个字符:p[j]
2.确定【选择】:dp[i][j] 分4种情况
- s[i] == p[j] 或者 p[j] == '?' :该字符匹配上
- 前一个子串状态: =dp[i-1][j-1]
- p[j] == '*':该字符可能匹配上"*"
- 匹配当前s[i],则又分为以下三种"*"的匹配情况,他们之间求OR:
- 匹配0次(匹配串的 j 跳过一个字符*,字符串的 i 跳过0个字符):=dp[i][j-1]
- 匹配1次(匹配串的 j 跳过一个字符*,字符串的 i 跳过一个字符):=dp[i-1][j-1]
- 匹配>1次(匹配串的 j 跳过0个字符,字符串的 i 跳过一个字符):=dp[i-1][j]
- 匹配当前s[i],则又分为以下三种"*"的匹配情况,他们之间求OR:
- 该字符未匹配
- false
3. dp[i][j]的含义:
字符串s的0~第 i 个字符,是否能被匹配串p的0~第 j 个字符,匹配上。
4. 状态转移:
dp[i][j]=
- (s[i] == p[j] 或者 p[j] == '.' ):=前一个子串状态:dp[i-1][j-1]
- (p[j] == '*'):
- 前一个字符匹配s当前字符,OR {
- 使*匹配0次:dp[i][j-1]
- 使*匹配1次:dp[i-1][j-1]
- 使*匹配>1次:dp[i-1][j] }
- 前一个字符匹配s当前字符,OR {
- 其他则不匹配:=false
5. base case:
- dp[i][0]=false:任意字符串s,匹配空串,除非空串自己,其他都为false。
- dp[0][0]=true
- dp[0][j]=true:当dp[0][j-1]==true && p[j]=='*'
- "***...*"只有这种情况能匹配任意空串字符串s。
代码参考:
1 class Solution { 2 //dp[i][j]:s[0-i],p[0-j] ismatch? 3 //case_1: s[i]==p[j] ==dp[i-1][j-1] 4 //case_2: p[j]=='?' ==dp[i-1][j-1] 5 //case_3: p[j]=='*' ==dp[i-1][j-1] or dp[i][j-1] or dp[i-1][j] 6 //base_case: dp[0][0]=true, dp[0][j]=(dp[0][j-1]&&p[j]==*), dp[i][0]=false 7 public: 8 bool isMatch(string s, string p) { 9 int sn = s.size(); 10 int pn = p.size(); 11 if(sn==0 && pn==0) return true; 12 if(pn==0) return false; 13 vector<vector<bool>> dp(sn+1, vector<bool>(pn+1, false)); 14 //base: 15 dp[0][0] = true; 16 for(int j=1; j<=pn; j++) dp[0][j] = (dp[0][j-1]&&p[j-1]=='*'); 17 if(sn>=1) dp[1][0] = false; 18 for(int i=1; i<=sn; i++) { 19 for(int j=1; j<=pn; j++) { 20 if(s[i-1]==p[j-1] || p[j-1]=='?'){ 21 dp[i][j] = dp[i-1][j-1]; 22 } else if(p[j-1]=='*') { 23 dp[i][j] = (dp[i-1][j-1] || dp[i][j-1] || dp[i-1][j]); 24 } 25 } 26 } 27 28 return dp[sn][pn]; 29 } 30 };
6. ♻️ 优化:降维打击
⚠️ 注意,由于5中初始化所有可能为false,
而在6降维打击后,每次使用的cell为上一层遍历残留数据,因此对【两个字符不相同】的情况,需要赋值false。
1 class Solution { 2 //dp[i][j]:s[0-i],p[0-j] ismatch? 3 //case_1: s[i]==p[j] ==dp[i-1][j-1] 4 //case_2: p[j]=='?' ==dp[i-1][j-1] 5 //case_3: p[j]=='*' ==dp[i-1][j-1] or dp[i][j-1] or dp[i-1][j] 6 //case_4: s[i]!=p[j] ==false 7 //base_case: dp[0][0]=true, dp[0][j]=(dp[0][j-1]&&p[j]==*), dp[i][0]=false 8 public: 9 bool isMatch(string s, string p) { 10 int sn = s.size(); 11 int pn = p.size(); 12 if(sn==0 && pn==0) return true; 13 if(pn==0) return false; 14 vector<bool>dp(pn+1, false); 15 //base: 16 dp[0] = true; 17 for(int j=1; j<=pn; j++) dp[j] = (dp[j-1]&&p[j-1]=='*'); 18 //if(sn>=1) dp[0] = false; 19 bool tmp; 20 bool memo; 21 for(int i=1; i<=sn; i++) { 22 tmp = dp[0]; 23 dp[0]=false; 24 for(int j=1; j<=pn; j++) { 25 memo = dp[j]; 26 if(s[i-1]==p[j-1] || p[j-1]=='?'){ 27 dp[j] = tmp; 28 } else if(p[j-1]=='*') { 29 dp[j] = (tmp || dp[j-1] || dp[j]); 30 } else { 31 dp[j] = false; 32 } 33 tmp = memo; 34 } 35 } 36 37 return dp[pn]; 38 } 39 };