LeetCode_0044. 通配符匹配,LeetCode_0010. 正则表达式匹配,带字母'*''?'的模式串匹配仅带字母的主串
LeetCode_0044. 通配符匹配
给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '?' 和 '*' 匹配规则的通配符匹配:
1. '?' 可以匹配任何单个字符。
2. '*' 可以匹配任意字符序列(包括空字符序列)。
3. 判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。
-
示例 1:
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。 -
示例 2:
输入:s = "aa", p = ""
输出:true
解释:'' 可以匹配任意字符串。 -
示例 3:
输入:s = "cb", p = "?a"
输出:false
解释:'?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。 -
提示:
0 <= s.length, p.length <= 2000 s 仅由小写英文字母组成 p 仅由小写英文字母、'?' 或 '*' 组成
分析
状态s[0..j] & p[0..i] 的匹配情况可以推出下一状态 s[0..j+1] & p[0..i/i+1] 的匹配情况。
存在'*'匹配0个或多个,因此状态推导关系不仅仅是一维的。
问题可以分解为若干 重叠子问题 :二者的子串匹配 --> 子问题之间存在 二维的状态推导关系 --> 二维动态规划 。
dp数组
设dp[i][j]表示s[0..j-1] & p[0..i-1]的匹配情况,true/false。
初始化
dp的初始化需要考虑空串 ,s为空 or p为空。
-
i = 0 时,p[0..i-1]为空串,仅能与空串(j = 0)匹配成功
dp[0][0] = true; -
j = 0 时,s[0..j-1]为空串,仅能与空串(i = 0)或'...'匹配成功
当p[0..i-1]是连续的'*'时,dp[i][0] = true;
状态推导
i = [1..p.length()],遍历p[0..p.length()-1]逐行推导:
-
若p[i-1] == '*',可不匹配字符,或匹配字符
dp[i][j] = dp[i-1][j] || dp[i][j-1] -
若p[i-1] == '?',匹配字符
dp[i][j] = dp[i-1][j-1] -
若p[i-1] == 字母,与s[j-1]比较,相等就匹配成功,不等就匹配失败
dp[i][j] = dp[i-1][j-1] && p[i-1] == s[j-1]
实现
dp数组的推导公式可知,dp[i]仅与dp[i-1]有关,可以压缩二维dp数组为一维,注意j=0时的dp值
class Solution {
public:
bool isMatch(string s, string p) {
const int sLen = s.length(), pLen = p.length();
vector<bool> dpLine(sLen + 1, false);
dpLine[0] = true;
int dpZeroTrue = 1;
for(; dpZeroTrue <= pLen && p[dpZeroTrue - 1] == '*'; ++dpZeroTrue);
// 字符串i-1对应dpi,字符串j-1对应dpj
for(int i = 1; i <= pLen; ++i) {
const char pi = p[i - 1];
if(pi == '*') {
dpLine[0] = i < dpZeroTrue;
for(int j = 1; j <= sLen; ++j) {
dpLine[j] = dpLine[j - 1] || dpLine[j];
}
} else if(pi == '?'){
for(int j = sLen; j > 0; --j) {
dpLine[j] = dpLine[j - 1];
}
dpLine[0] = i < dpZeroTrue;
} else {
for(int j = sLen; j > 0; --j) {
dpLine[j] = dpLine[j - 1] && pi == s[j - 1];
}
dpLine[0] = i < dpZeroTrue;
}
}
return dpLine.back();
}
};
LeetCode_0010. 正则表达式匹配
给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '*' 和 '.' 匹配规则的通配符匹配:
1. '*' 可以匹配0个或多个前序字符。
2. '.' 可以匹配任意字符。
3. 判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。
分析
dp数组
同上,设dp[i][j]表示s[0..j-1] & p[0..i-1]的匹配情况,true/false。
初始化
- i = 0时,同上,p[0..i-1]为空串,仅能匹配空串。
dp[0][0] = true - j = 0时,s[0..j-1]为空串,考虑'' 的匹配情况。
若p[0..i-1]是连续的"x"组合,则在'*'的位置dp值true。
状态推导
i = [1..p.length()],遍历p[0..p.length()-1]逐行推导:
-
若p[i-1] == '*',可不匹配字符,或匹配前序字符
dp[i][j] = dp[i-2][j] || (dp[i][j-2] && (p[i-2] == s[j-1] || p[i-2] == '.')) -
若p[i-1] == '.',匹配字符
dp[i][j] = dp[i-1][j-1] -
若p[i-1] == 字母,与s[j-1]比较,相等就匹配成功,不等就匹配失败
dp[i][j] = dp[i-1][j-1] && p[i-1] == s[j-1]
实现
dp数组的推导公式可知,dp[i]仅与dp[i-1]有关,可以压缩二维dp数组为一维,注意j=0时的dp值
bool isMatch(string s, string p) {
const int sLen = s.length(), pLen = p.length();
vector<vector<bool>> dp(pLen + 1, vector<bool>(sLen + 1, false));
dp[0][0] = true;
for(int i = 2; i <= pLen; i += 2) {
if(p[i - 1] == '*') {
dp[i][0] = true;
} else {
break;
}
}
for(int i = 1; i <= pLen; ++i) {
const char pi = p[i - 1];
if(pi == '*') {
const char pre = p[i - 2];
for(int j = 1; j <= sLen; ++j) {
dp[i][j] = dp[i - 2][j] || (dp[i][j - 1] && (pre == s[j - 1] || pre == '.'));
}
} else if(pi == '.') {
for(int j = 1; j <= sLen; ++j) {
dp[i][j] = dp[i - 1][j - 1];
}
} else {
for(int j = 1; j <= sLen; ++j) {
dp[i][j] = dp[i - 1][j - 1] && pi == s[j - 1];
}
}
}
return dp[pLen][sLen];
}
进阶
类似的正则表达式题目还有很多:主串和模式串按照一定的规则完全匹配。按匹配规则,通配符能匹配可选数量的主串字符。
不同规则下,dp数组的初始化和推导公式有所不同。
题目描述
笔试中遇到一题,题目大致如下:
给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '?' 、 '*' 和 '.' 匹配规则的通配符匹配:
1. '?' 可以匹配1个或多个前序字符。
2. '*' 可以匹配0个或多个前序字符。
3. '.' 可以匹配任意字符。
4. 判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。
dp数组
同上,设dp[i][j]表示s[0..j-1] & p[0..i-1]的匹配情况,true/false。
初始化
- i = 0时,同上,p[0..i-1]为空串,仅能匹配空串。
dp[0][0] = true - j = 0时,s[0..j-1]为空串.由于'?' 和 '' 的匹配情况与前序字符有关,所以要多加考虑。笔试是就是这里出现了疏漏。
若p[0..i-1]是连续的"x"组合,则在'*'的位置dp值true。
状态推导
i = [1..p.length()],遍历p[0..p.length()-1]逐行推导:
-
若p[i-1] == '*',可不匹配字符,或匹配前序字符
dp[i][j] = dp[i-2][j] || (dp[i][j-2] && (p[i-2] == s[j-1] || p[i-2] == '.')) -
若p[i-1] == '?',可匹配前序字符
dp[i][j] = dp[i][j-2] && (p[i-2] == s[j-1] || p[i-2] == '.') -
若p[i-1] == '.',匹配字符
dp[i][j] = dp[i-1][j-1] -
若p[i-1] == 字母,与s[j-1]比较,相等就匹配成功,不等就匹配失败
dp[i][j] = dp[i-1][j-1] && p[i-1] == s[j-1]