题目描述
解题思路
首先,将str和pattern都从下标1开始存储。
string str, pattern;
int n, m;
void input(){
cin >> str >> pattern;
str = ' ' + str;
pattern = ' ' + pattern;
n = str.length();
m = pattern.length();
}
备忘录
定义dp[i][j]
的含义为:str的前i个字符str[1...i]
与pattern的前j个字符pattern[1...j]
是否匹配。
bool dp[1001][1001]; // str的前i个字符 与 pattern的前j个字符 是否匹配
备忘录的初始化
初始化考虑的是dp[i][0]
和dp[0][j]
,也就是当pattern为空字符串或str为空字符串时的情况,有时也需要将两者都为空的情况dp[0][0]
单独讨论。在开始讨论之前需要注意题目中的要求:'*'
也可以匹配0次。
- 空pattern 只能与 空str匹配,与 任何非空str 都不匹配,因此
dp[0][0] = true
,dp[1...n-1][0] = false
; - 空str 只能与类似
a*b*c*d*e*
这样的pattern匹配(令其中的每一个*
都匹配0次)。
// init
dp[0][0] = true; // 空pattern 只与 空str 匹配
// dp[i][0]
for(int i=1; i<n; i++){
dp[i][0] = false; // 空pattern 与 任何非空str 都不匹配
}
// dp[0][j]
for(int j=1; j<m; j++){
if(j%2 == 0 && pattern[j] == '*'){
dp[0][j] = dp[0][j-2]; // 空str 只与类似 a*b*c* 的pattern匹配
}else{
dp[0][j] = false;
}
}
自底向上计算
自底向上计算就是根据当前问题与子问题之间的关系(也称“状态转移规律”)按照一定地顺序对备忘录中的表项进行计算。
根据str[i]
与pattern[j]
的情况进行分类讨论,str[i]
一定是小写字母,pattern[j]
可能是小写字母、'*'
或'.'
,所以分类讨论时主要考虑的是pattern[j]
的情况。
- 如果
pattern[j]
也是小写字母,那么:
- 当
pattern[j] == str[i]
时:pattern[j]
可以与str[i]
匹配,并且用掉了pattern[j]
;因此只要str[1...i-1]
能与pattern[1...j-1]
匹配,在str[1...i-1]
后面加上字符str[i]
之后也能与「pattern[1...j-1]
加上pattern[j]
」匹配,当前问题的解转移为子问题的解:dp[i][j] = dp[i-1][j-1]
。 - 当
pattern[j] != str[i]
时:str[i]
无法与pattern[j]
匹配,此时无论前面的字符的匹配情况如何,最后一位始终无法匹配,因此dp[i][j] = false
。
- 如果pattern[j]是
'*'
,需要关注'*'
的前一个字符pattern[j-1]
:
- 2.1)
pattern[j-1] == str[i]
或pattern[j-1] == '.'
- 2.1.1)如果
pattern[j-1] == str[i]
或pattern[j-1] == '.'
,那么str[i]
就可以与pattern[j-1]*
匹配,这次匹配用掉了一个pattern[j-1]*
,但由于pattern[j-1]*
可以匹配任意多次,用掉一次之后还可以继续匹配;因此只要str[1...i-1]
能与pattern[1...j]
匹配,str[1...i]
就能与pattern[1...j]
匹配:dp[i][j] = dp[i-1][j]
- 2.1.2)如果
pattern[j-1] == str[i]
或pattern[j-1] == '.'
,也可以不用pattern[j-1]*
来匹配str[i],可以令pattern[j-1]*
匹配0次,然后用pattern[j-1]*
之前的正则表达式进行匹配,这样当前问题就直接转换为str[i]
与pattern[j-2]
的匹配问题了:dp[i][j] = dp[i][j-2]
- 2.1.1)如果
对于pattern[j-1] == str[i]
或pattern[j-1] == '.'
,上面两种情况中任意一种能够匹配成功都可以,所以最终的状态转移规律为:dp[i][j] = dp[i-1][j] || dp[i][j-2]
。
- 2.2)
pattern[j-1] != str[i]
:str[i]和pattern[j-1]*
不匹配,让pattern[j-1]*
出现0次,然后用pattern[j-1]*
之前的正则表达式进行匹配,这样当前问题就直接转换为str[i]
与pattern[j-2]
的匹配问题了:dp[i][j] = dp[i][j-2]
。
- 如果
pattern[j]
是'.'
:str[i]
一定可以与pattern[j]
匹配,并且用掉了pattern[j]
,因此dp[i][j] = dp[i-1][j-1]
。
//bottom-up calc
for(int i=1; i<n; i++){
for(int j=1; j<m; j++){
if(pattern[j] == str[i] || pattern[j] == '.'){
// pattern[j]可以与str[i]匹配,并且用掉了pattern[j]
dp[i][j] = dp[i-1][j-1];
continue;
}
if(pattern[j] == '*'
&& (pattern[j-1] == str[i] || pattern[j-1]=='.')
){
// str[i]可以与「pattern[j-1]*」匹配,用掉了一个*
// 也可以令「pattern[j-1]*」出现0次,与pattern[1...j-2]匹配
dp[i][j] = dp[i-1][j] || dp[i][j-2];
continue;
}
if(pattern[j] == '*' && pattern[j-1] != str[i]){
// str[i]和「pattern[j-1]*」不匹配,让「pattern[j-1]*」出现0次
dp[i][j] = dp[i][j-2];
continue;
}
// pattern[j]是字母但 pattern[j]!=str[i]
dp[i][j] = false;
}
}
return dp[n-1][m-1];
C++ 代码实现
#include <iostream>
using namespace std;
string str, pattern;
int n, m;
void input(){
cin >> str >> pattern;
str = ' ' + str;
pattern = ' ' + pattern;
n = str.length();
m = pattern.length();
}
bool dp[1001][1001]; // str的前i个字符 与 pattern的前j个字符 是否匹配
bool solve(){
// init
dp[0][0] = true; // 空pattern 只与 空str 匹配
// dp[i][0]
for(int i=1; i<n; i++){
dp[i][0] = false; // 空pattern 与 任何非空str 都不匹配
}
// dp[0][j]
for(int j=1; j<m; j++){
if(j%2 == 0 && pattern[j] == '*'){
dp[0][j] = dp[0][j-2]; // 空str 只与类似 a*b*c* 的pattern匹配
}else{
dp[0][j] = false;
}
}
//bottom-up calc
for(int i=1; i<n; i++){
for(int j=1; j<m; j++){
if(pattern[j] == str[i] || pattern[j] == '.'){
// pattern[j]可以与str[i]匹配,并且用掉了pattern[j]
dp[i][j] = dp[i-1][j-1];
continue;
}
if(pattern[j] == '*'
&& (pattern[j-1] == str[i] || pattern[j-1]=='.')
){
// str[i]可以与「pattern[j-1]*」匹配,用掉了一个*
// 也可以令「pattern[j-1]*」出现0次,与pattern[1...j-2]匹配
dp[i][j] = dp[i-1][j] || dp[i][j-2];
continue;
}
if(pattern[j] == '*' && pattern[j-1] != str[i]){
// str[i]和「pattern[j-1]*」不匹配,让「pattern[j-1]*」出现0次
dp[i][j] = dp[i][j-2];
continue;
}
// pattern[j]是字母但 pattern[j]!=str[i]
dp[i][j] = false;
}
}
return dp[n-1][m-1];
}
int main() {
input();
solve();
if(dp[n-1][m-1]) cout << "true";
else cout << "false";
}
// 64 位输出请用 printf("%lld")