一、剑指offer
链接
分析:
理解题意:文本串中没有.和,但在正则表达式模式串中有。*
首先,考虑特殊情况:
- 两个字符串都为空,返回true
- 当第一个字符串不空,而第二个字符串空了,返回false(因为这样,就无法匹配成功了,而如果第一个字符串空了,第二个字符串非空,还是可能匹配成功的,比如第二个字符串是“aaaa”,由于‘*’之前的元素可以出现0次,所以有可能匹配成功)
当模式中的第二个字符不是“*”时:
- 如果字符串第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的。
- 如果 字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。
而当模式中的第二个字符是“*”时:
(1)如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。(x*表示0个x)
(2)如果字符串第一个字符跟模式第一个字符匹配,可以有3种匹配方式:
- 模式后移2字符,相当于x被忽略;(x表示0个x)
- 文本串后移1字符,模式串后移2字符;(x*表示一个x)
- 有限状态机不变:文本串后移1字符,模式串不变,即继续匹配字符下一位,因为*可以匹配多位;( x *表示多个x,包含上一种情况)
剑指参考答案
class Solution{
public:
bool match(const char* str, const char* pattern)
{
if(str == nullptr || pattern == nullptr)
return false;
return matchCore(str, pattern);
}
bool matchCore(const char* str, const char* pattern)
{
if(*str == '\0' && *pattern == '\0')
return true;
if(*str != '\0' && *pattern == '\0')
return false;
if(*(pattern + 1) == '*')
{
if(*pattern == *str || (*pattern == '.' && *str != '\0'))
// 进入有限状态机的下一个状态
return matchCore(str + 1, pattern + 2)
// 继续留在有限状态机的当前状态
|| matchCore(str + 1, pattern)
// 略过一个'*'
|| matchCore(str, pattern + 2);
else
// 略过一个'*'
return matchCore(str, pattern + 2);
}
//省略了else命令
if(*str == *pattern || (*pattern == '.' && *str != '\0'))
return matchCore(str + 1, pattern + 1);
return false;
}
Reference
二、备忘录递归-由顶向下
链接
https://labuladong.online/algo/dynamic-programming/regular-expression-matching/
class Solution {
public:
// 备忘录
vector<vector<int>> memo;
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
// memo[i][j]: s[i...]和p[j...]匹配的结果记录
memo = vector<vector<int>>(m, vector<int>(n, -1));
// 指针 i,j 从索引 0 开始移动
// dp(s,i,p,j): s[i...]和p[j...]是否能够匹配
return dp(s, 0, p, 0);
}
// 计算 p[j..] 是否匹配 s[i..]
bool dp(string& s, int i, string& p, int j) {
int m = s.size(), n = p.size();
// base case
if (j == n) {
return i == m;
}
if (i == m) {
if ((n - j) % 2 == 1) {
return false;
}
for (; j + 1 < n; j += 2) {
if (p[j + 1] != '*') {
return false;
}
}
return true;
}
// 查备忘录,防止重复计算
if (memo[i][j] != -1) {
return memo[i][j];
}
bool res = false;
if (s[i] == p[j] || p[j] == '.') {
if (j < n - 1 && p[j + 1] == '*') {
res = dp(s, i, p, j + 2) // 有通配符'*', 匹配0个
|| dp(s, i + 1, p, j); // 有通配符'*', 匹配多个
} else {
res = dp(s, i + 1, p, j + 1); // 无通配符'*’,匹配一次
}
} else {
if (j < n - 1 && p[j + 1] == '*') {
res = dp(s, i, p, j + 2); // 有通配符'*', 匹配0个
} else {
res = false;
}
}
// 将当前结果记入备忘录
memo[i][j] = res;
return res;
}
};
三、动态规划-由底向上
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.length(), n = p.length();
// dp[i][j] 表示 s[i..] 是否匹配 p[j..]
vector<vector<bool>> dp(m+1, vector<bool>(n+1, false));
// base case:
// 空串匹配空串
dp[m][n] = true;
// s[i..] 匹配空串为 false
for(int i = 0; i < m; i++) {
dp[i][n] = false;
}
// 空串匹配 p[j..],主要看 p[j..] 是否是 x*z*y*.. 形式
for(int j = 0; j < n; j++) {
if((n-j) % 2 == 1)
dp[m][j] = false;
else {
bool res = true;
for(int jj = j; jj < n-1; jj += 2)
if(p[jj+1] != '*')
res = false;
dp[m][j] = res;
}
}
// 从后往前,dp[i][j] 需要的是其后面的元素
for(int i = m-1; i >= 0; i--) {
for(int j = n-1; j >= 0; j--) {
if(s[i] == p[j] || p[j] == '.') {
// “j+1 < n“ ,因为 n 此时有效,表示末尾的空串,需要考虑。
if(j+1 < n && p[j+1] == '*') {
dp[i][j] = dp[i][j+2] || dp[i+1][j];
}else {
dp[i][j] = dp[i+1][j+1];
}
} else {
// 同上
if(j+1 < n && p[j+1] == '*') {
dp[i][j] = dp[i][j+2];
}else {
dp[i][j] = false;
}
}
}
}
return dp[0][0];
}
};