【牛客网动态规划专项】DP21 正则表达式匹配

题目描述

在这里插入图片描述
在这里插入图片描述

解题思路

首先,将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次

  1. 空pattern 只能与 空str匹配,与 任何非空str 都不匹配,因此dp[0][0] = truedp[1...n-1][0] = false
  2. 空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]的情况。

  1. 如果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
  1. 如果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]

对于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]
  1. 如果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")
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值