【牛客网动态规划专项】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")
正则表达式匹配是一个经典的算法问题,主要是判断一个字符串是否能够完全匹配给定的正则表式。 在LeetCode上,也有一道关于正则表达式匹配的题目,题目编号是10。这道题目要求实现一个支持 '.' 和 '*' 的正则表达式匹配,其中 '.' 可以匹配任意单个字符,'*' 可以匹配零个或多个前面的元素。 解决这道题可以使用动态规划的思想,具体的思路如下: 1. 创建一个二维数组dp,dp[i][j]表示s的前i个字符与p的前j个字符是否匹配。 2. 初始化dp为true,表示空字符串与空正则表达式是匹配的。 3. 初始化dp[i]为false,表示非空字符串与空正则表达式是不匹配的。 4. 初始化dp[j],如果p[j-1]是"*",则dp[j]的值取决于dp[j-2]的值,表示将p[j-2]与p[j-1]去掉后的正则表达式是否匹配空字符串。 5. 对于其它的dp[i][j],分成两种情况: - 如果p[j-1]是"."或者与s[i-1]相等,则dp[i][j]的值取决于dp[i-1][j-1]的值,表示将s[i-1]和p[j-1]去掉后的字符串是否匹配。 - 如果p[j-1]是"*",则dp[i][j]的值取决于以下两种情况: - dp[i][j-2]的值,表示将p[j-2]和p[j-1]去掉后的正则表达式是否匹配s的前i个字符。 - dp[i-1][j]的值,表示将s[i-1]与p[j-2]匹配后的字符串是否匹配p的前j个字符。 6. 最后返回dp[s.length()][p.length()]的值,表示整个字符串s与正则表达式p是否完全匹配。 以上是一种使用动态规划解决正则表达式匹配问题的思路,具体的实现可以参考LeetCode官方提供的递归思路的解法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [LeetCode算法 —— 正则表达式匹配(详解官方动态规划思想)](https://blog.csdn.net/weixin_42100963/article/details/106953141)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值