文章目录
一、通配符匹配
题目描述:
示例 1:
示例 2:
示例 3:
1.1 思路分析
我们可以分别以i和j表示s[0~i]
和p[0~j]
是否能成功匹配。
那么这里就只用讨论i
和j
的位置,有四种情况:
1️⃣ 如果s[i] == p[j]
,那么我们就只用判断dp[i - 1][j - 1]
是否能成功匹配,如果能成功匹配,那么说明加上i
和j
的位置也能成功匹配。
状态转移方程:dp[i][j] = s[i] == p[j] && dp[i - 1][j - 1]
2️⃣ 如果p[j] == '?'
那么说明此时s[i]不管是什么都可以,只需要判断dp[i - 1][j - 1]
是否能成功匹配,就跟上面一样。
状态转移方程:dp[i][j] = p[j] == '?' && dp[i - 1][j - 1]
3️⃣ 如果p[j] == '*'
,这里的情况就比较多,因为它可以变成0个或多个字符:
这么多情况只要有一种情况满足条件即可。
状态转移方程:
for(int k = 0; k <= i; k++)
{
if(dp[i - k][j - 1])
{
dp[i][j] = true;
break;
}
}
4️⃣ 如果p[j] != '?' && p[j] != '*' && p[j] != s[i]
,那么说明不能匹配。
1.2 初始化处理
我们看到状态转移方程会用到i-1
和j- 1
,所以dp表可以多开一维,而为了不改变下标的映射关系,我们可以在s串和p串的开头各自添加一个字符。
接下来就是初始化,首先dp[0][0]
就代表两个空串,一定能匹配。所以dp[0][0] = true
;
其次还有*
在p串开头的位置出现,因为*
可以变成空串,所以只要是开头的*
都可以跟s[0]匹配成功:dp[0][j] = true
;
1.3 代码
class Solution {
public:
bool isMatch(string s, string p) {
s = " " + s;
p = " " + p;
int n = s.size(), m = p.size();
vector<vector<bool>> dp(n, vector<bool>(m));
dp[0][0] = true;
for(int j = 1; j < m; j++)
{
if(p[j] == '*')
{
dp[0][j] = true;
}
else break;
}
for(int i = 1; i < n; i++)
{
for(int j = 1; j < m; j++)
{
if((p[j] == '?' && dp[i - 1][j - 1]))
{
dp[i][j] = true;
}
else if(s[i] == p[j] && dp[i - 1][j - 1])
{
dp[i][j] = true;
}
else if(p[j] == '*')
{
for(int k = 0; k <= i; k++)
{
if(dp[i - k][j - 1])
{
dp[i][j] = true;
break;
}
}
}
}
}
return dp[n - 1][m - 1];
}
};
1.4 优化
p[j] == '*'
这种情况其实可以写成:
而经过观察可以再写出一个式子:
经过观察可以发现蓝色框框圈起来的部分是相等的
所以可以写成:
class Solution {
public:
bool isMatch(string s, string p) {
s = " " + s;
p = " " + p;
int n = s.size(), m = p.size();
vector<vector<bool>> dp(n, vector<bool>(m));
dp[0][0] = true;
for(int j = 1; j < m; j++)
{
if(p[j] == '*') dp[0][j] = true;
else break;
}
for(int i = 1; i < n; i++)
{
for(int j = 1; j < m; j++)
{
if(dp[i - 1][j - 1] && (p[j] == '?' || s[i] == p[j])) dp[i][j] = true;
else if(p[j] == '*')
{
dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
}
}
}
return dp[n - 1][m - 1];
}
};
二、正则表达式匹配
题目描述:
示例 1:
示例 2:
示例 3:
这里的.
字符跟上面一道题的?
作用是一样的。但是*
字符又区别,它的作用是把*
字符的前一个字符重复0次或者多次,比方说"a*"
它可以变成:""
,"a"
,"aa"
,"aaa"
……
2.1 思路分析
这里的有些情况跟上面的重复:
当dp[i - 1][j - 1] && (p[j] == '.' || s[i] == p[j])
的时候,dp[i][j]=true
。
接下来只剩p[j] == '*'
的情况:
这里要分情况讨论j
的前一个元素,如果前一个元素是.
,那么也就是可以变成任意的多个字符,既然要匹配多个字符,那么又是跟上面一个题一样要讨论到底变成多长。因为上面讲过优化版,所以这里直接写:
接下来如果前面一个字符是普通字符:
这里解释以下:空自然不用说,当只变成长度为1的字符串时,首先要判断j
的前一个字符是否等于s[i]
,如果不相等就不用考虑前面的了。
2.2 初始化设置
还是跟上面一样多加一维,然后让dp[0][0] = true
,接下来就是看p的前面全部是x*x*
……这种字符串的情况:
for(int j = 2; j < m; j += 2)
{
if(p[j] == '*') dp[0][j] = true;
else break;
}
2.3 代码
class Solution {
public:
bool isMatch(string s, string p) {
s = " " + s;
p = " " + p;
int n = s.size(), m = p.size();
vector<vector<bool>> dp(n, vector<bool>(m + 1));
dp[0][0] = true;
for(int j = 2; j < m; j += 2)
{
if(p[j] == '*') dp[0][j] = true;
else break;
}
for(int i = 1; i < n; i++)
{
for(int j = 1; j < m; j++)
{
if(p[j] == '*')
{
if(p[j - 1] == '.')
{
dp[i][j] = dp[i][j - 2] || dp[i - 1][j];
}
else
{
dp[i][j] = dp[i][j - 2] || (s[i] == p[j - 1] && dp[i - 1][j]);
}
}
else
{
dp[i][j] = dp[i - 1][j - 1] && (p[j] == '.' || s[i] == p[j]);
}
}
}
return dp[n - 1][m - 1];
}
};