10. 正则表达式匹配

文章来自公众号 技术乱舞

原文链接 :https://mp.weixin.qq.com/s/M0mGAwuYK5lRLZN8Eve5xA

正则表达式匹配

目录

正则表达式匹配

关注「技术乱舞」,一起进步!

题目描述

示例 1:

示例 2:

示例 3:

提示:

题目分析

执行过程

小结

题解代码

总结

欢迎关注 #公众号:技术乱舞 一起交流


刷题目前遇到动态规划题目中最难的一题,今天就与大家分享一下具体解题过程,在菜的路上越来约菜。

如果追忆会荡起涟漪,那么今天的秋红落叶和晴空万里都归你
https://aeneag.xyz

关注「技术乱舞」,一起进步!

LeetCode 第十题,正则表达式匹配,是目前做过动态规划题目中最难的一题,个人认为也是这 100 题中最难的一题,也是结束 100 热题的最后一道。

 

题目描述

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.'和 '*'** ** 的正则表达式匹配。

  • '.'** ** 匹配任意单个字符
  • '*'** ** 匹配零个或多个前面的那一个元素

所谓匹配,是要涵盖 整个字符串 s 的,而不是部分字符串。

示例 1:

输入 :s = "aa" p = "a"

输出 :false

解释 :"a" 无法匹配 "aa" 整个字符串。

示例 2:

输入 :s = "aa" p = "a*"*

输出 :true

解释 :因为 '*'** 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a' 。因此,字符串 "aa" 可被视为 'a' ** 重复了一次。

示例 3:

输入 :s = "mississippi" p = "misis p*."

输出 :false

提示:

  • 1 <= s.length <= 20
  • 1 <= p.length <= 30
  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
  • 保证每次出现字符 * 时,前面都匹配到有效的字符

题目分析

执行过程

看到题目首先会想到用动态规划解题,dp 数组到底表示什么呢?

首先建立 S 和 P 的二维数组:

 

首先开始建立一个二维数组,最开始的样子,图上第一个 True 是行列都为空的时候,此时字符匹配,所以第一个是 True。

那么接下来对这个二维数组初始化:

 

第一列所有的字符都与空字符无法匹配,所以全部填入 False。

第一行的数据,与空字符无法匹配,填入 False,当然此时一定要注意,如果当前列是 *** ** 号,那么就要看当前列的前二列是什么,如果是 True,那么当前列也是 True。用代码表示就是 dp[i][j] = dp[i][j-2]

初始化完成后,就可以开始填表了:

 

填表规则:

  • 如果没有遇到 *****.** ** 只要行列字符不相同就填入 False
  • 如果遇到行列相同的情况,那么就看它左上方的状态是什么,如上图,是 True 就填 True,代码表示dp[i][j] = dp[i-1][j-1]

为什么这么填呢,行列同时加入一个字符,并且两个字符相同,那如果看是否匹配那就看没加入之前是什么状态,用数组表示,就是查看左上方的状态。

接下来处理遇到 *** ** 号时:

 

  • 如上图所示,如果遇到***号,就看当前行前二位置的状态,代码表示 dp[i][j] = dp[i][j-2] **,如果是 True,那么这里也是 True,如果不是,下文讲解。

 

  • 上图就是遇到了*号,当前行前二位置不匹配的情况,那么现在就查看 p[i-1] == s[i],也就是图上*** ** 之前字符与当前行的字符是否相同,图中相同,那么当前状态就是dp[i][j] = dp[i-1][j],就是复制上一行,当前位置的状态 ,只要不满足当前情况都是 False。为什么复制上一行相同位置的状态,因为如果之前是 True,那么这个时候又多一个相同的符号也可以,如果不是 True,加上一个,同样也不匹配。
  •  

  • 最后就是.** ** 号情况,这个遇到任何符号,都是代表匹配的意思,所以如上图所示,复制左上方状态即可。

遵循上文规则,即可在图表的最下方获得结果

 

那么看一组准确的求解过程

 

小结

dp 状态数组更新过程:

  1. 初始化数组全部为 False,如果是*** ** 号,dp[i][j] = dp[i][j-2]
  2. 行列是相同字符或者遇到.号,状态与左上角相同,dp[i][j] = dp[i-1][j-1]
  3. 遇到*号,两种情况
  • 如果前二位置 True,那么也是 True,dp[i][j] = dp[i][j-2]
  • 上面的情况不是 True,那么,查看p[i-1] == s[i]** ** 是否相等,如果相等,此刻的状态与上一行当前位置状态相同,dp[i][j] = dp[i-1][j]

上面的过程也就是代码要实现的过程。

题解代码

代码中已详加注释,把分析过程实现即可。

class Solution {
public:
    //写代码时,犯了一个错误,就是用for循环获取s和p中的某个字符,导致初始化和计算过程出错
    bool isMatch(string s, string p) {
        //新建一个二维数组,并初始化,全部为False,注意要比s和p大一个
        vector<vector<bool>> dp(s.size()+1,vector<bool>(p.size()+1,false));
        //第一个状态设置为True
        dp[0][0] = true;
        //for循环初始化dp数组,遵循之前分析的过程
        for(int i = 1 ; i < dp[0].size() ; ++i){
            // for(auto &c : p){ //出错的地方,之前用for循环,脑袋卡壳了
            //获取当前行的第i个值
            char c = p[i-1];
            //查看是否匹配
            if(c == '*' )
                dp[0][i] = dp[0][i-2];
            //  }
        }
        //双for循环,填写二位数组状态值
        for(int i = 1 ; i < dp.size() ; ++i){
            //获取当前列的第i个值
            char c = s[i-1];
            for(int j = 1 ; j < dp[i].size() ; ++j){
                //获取当前行的第j个值
                char m = p[j-1];
                //比较字符
                if(c == m || m == '.'){
                    dp[i][j] = dp[i-1][j-1];
                }else if(m == '*'){//如果是*号,接下来要干什么
                    //这里if防止数组越界 j > 1
                    if(j > 1){
                        //if 判断语句,如图分析一般用代码实现
                        if(dp[i][j-2] == true)//前二位置是true,那么这里也是true
                            dp[i][j] = true;
                        else{
                            //获取前一个位置的字符
                            char m_prv = p[j-2];
                            //判断是否相同,如果相同,就与上一行当前位置状态一样
                            if(m_prv == c || m_prv == '.')
                                dp[i][j] = dp[i-1][j];
                        }
                    }
                }
            }
        }
        return dp[s.size()][p.size()];
    }
};

代码可以在 LeetCode 上完美运行,如果想要推理它的状态填表过程,提供线下代码。

#include <iostream>
#include <vector>
using namespace std;
//写代码时,犯了一个错误,就是用for循环获取s和p中的某个字符,导致初始化和计算过程出错
bool isMatch(string s, string p) {
    //新建一个二维数组,并初始化,全部为False,注意要比s和p大一个
    vector<vector<bool>> dp(s.size()+1,vector<bool>(p.size()+1,false));
    //第一个状态设置为True
    dp[0][0] = true;
    //for循环初始化dp数组,遵循之前分析的过程
    for(int i = 1 ; i < dp[0].size() ; ++i){
        // for(auto &c : p){ //出错的地方,之前用for循环,脑袋卡壳了
        //获取当前行的第i个值
        char c = p[i-1];
        //查看是否匹配
        if(c == '*' && i > 1)
            dp[0][i] = dp[0][i-2];
        if(i == 1 && c == '*')
            dp[0][i] = true;
        //  }
    }
    //双for循环,填写二位数组状态值
    for(int i = 1 ; i < dp.size() ; ++i){
        //获取当前列的第i个值
        char c = s[i-1];
        for(int j = 1 ; j < dp[i].size() ; ++j){
            //获取当前行的第j个值
            char m = p[j-1];
            //比较字符
            if(c == m || m == '.'){
                dp[i][j] = dp[i-1][j-1];
            }else if(m == '*'){//如果是*号,接下来要干什么
                //这里if防止数组越界 j > 1
                if(j > 1){
                    //if 判断语句,如图分析一般用代码实现
                    if(dp[i][j-2] == true)//前二位置是true,那么这里也是true
                        dp[i][j] = true;
                    else{
                        //获取前一个位置的字符
                        char m_prv = p[j-2];
                        //判断是否相同,如果相同,就与上一行当前位置状态一样
                        if(m_prv == c || m_prv == '.')
                            dp[i][j] = dp[i-1][j];
                    }
                }
            }
        }
    }
    printf("     |");
    for(int i = 0 ; i < p.size();++i)
        printf("  %c |",p[i]);
    printf("\n");
    for(int i = 0 ; i < dp.size() ; ++i){
        if(i > 0)
            printf("%c",s[i-1]);
        for(int j = 0 ; j < dp[0].size();++j){
            if(dp[i][j])
                if(i == 0)
                    printf("   T |");
                else
                    printf("  T |");
            else
                printf("  F |");
        }
        printf("\n");
    }
    return dp[s.size()][p.size()];
}
int main(void) {
    std::cout << "Hello, World!" << std::endl;
    string s="mississippi";
    string p="mis*is*ip*.";
    printf("s=\"mississippi\"\n");
    printf("p=\"mis*is*ip*.\"\n");
    isMatch(s,p);
    return 0;
}

总结

动态规划最难的就是如何想出状态方程,算法博大精深,值得一直学习。今天的分享就到这了,如果你喜欢我的文章,欢迎关注,可以点点下方在看,随缘分享技术文与生活文。

欢迎关注 #公众号:技术乱舞 一起交流

灵魂碰撞

 

<think>首先,用户的问题是:“我想查找使用正则表达式匹配其他正则表达式匹配结果的方法 请问正则表达式匹配其他正则表达式匹配结果的方法”,翻译成英文是:“I want to find a method to use regex to match the results of other regex matches. How do I match regex results with regex?” 用户提到了几个引用,我需要参考它们。引用[1]是关于匹配浮点数的正则表达式,引用[2]是关于匹配IP地址和一些其他模式的正则表达式,引用[3]是关于匹配${xxx}这样的模板字符串的正则表达式。 用户的核心问题是:如何用正则表达式匹配其他正则表达式匹配结果。这意味着,用户可能有一个文本,其中包含一些已经由另一个正则表达式匹配的部分,用户想用一个新的正则表达式匹配那些特定的匹配结果。 在正则表达式中,匹配其他正则表达式匹配结果通常不是一个直接的功能,因为正则表达式是用于匹配文本模式的,而不是匹配其他匹配结果。但是,用户可能在问如何捕获和处理子匹配者在嵌套匹配上下文中使用正则表达式。 回顾引用[3],它讨论了匹配${xxx},并改进正则表达式来处理空格和操作符。这表明用户可能对捕获组和精确匹配感兴趣。 可能的解释: 1. **嵌套匹配**:用户可能想在一个文本中匹配某个模式,然后对那些匹配到的部分应用另一个正则表达式。这不是用单个正则表达式完成的,而是需要编程实现的:先用第一个正则表达式匹配所有位置,然后对每个匹配结果应用第二个正则表达式。 2. **引用捕获组**:在正则表达式中,捕获组可以存储部分匹配,用户可能想匹配这些捕获组的值。但正则表达式本身不能动态引用先前匹配的结果(除非在替换部分使用反向引用)。 用户说“匹配其他正则表达式匹配结果”,所以更可能指的是处理第一个正则表达式的输出。 在回答中,我需要: - 使用系统级指令:所有行内数学表达式用$...$,独立公式用$$...$$。 - 使用中文回答。 - 生成相关问题。 - 引用末尾添加引用标识。 结构化回答: 1. **解释问题**:澄清正则表达式匹配结果的本质。 2. **方法**:描述如何在编程中实现匹配匹配结果。 - 步骤1:使用第一个正则表达式扫描文本,获取所有匹配对象字符串。 - 步骤2:对每个匹配的对象字符串应用第二个正则表达式。 3. **举例**:基于引用来举例,比如引用[3]中的${xxx}匹配。 - 例如:首先用引用[3]的正则匹配所有${...},然后对每个匹配应用另一个正则表达式提取内容。 4. **正则表达式本身**:如果用户想在单个正则表达式中实现,可以使用捕获组和贪婪/非贪婪匹配,但会有限制。 在引用[3]中,它使用了\$\{(.*?)\}来匹配${和}之间的内容,然后可以进一步处理匹配的内容。 回答核心内容: - 正则表达式匹配结果是文本片段,因此可以用另一个正则表达式匹配它们。 - 在代码中,通常是先收集所有匹配,然后迭代处理。 生成相关问题: 1. 如何在Python中使用正则表达式处理嵌套匹配? 2. 正则表达式中捕获组的应用方法? 3. 如何处理复杂的文本模板匹配? 确保回答真实可靠。 最终回答大纲: - 介绍:正则表达式匹配结果是字符串,因此可以用其他正则表达式匹配。 - 详细方法:描述编程实现。 - 代码示例:使用Python的re模块,因为引用中有Python。 - 引用相关部分:比如引用[3]。 - 相关问题。 </think>### 正则表达式匹配其他正则表达式匹配结果的方法 在使用正则表达式时,匹配其他正则表达式匹配结果通常不是直接通过单个正则表达式完成的,因为正则表达式本身是静态模式匹配工具,无法直接动态引用其他匹配的文本片段。相反,这需要结合编程逻辑实现:**先用第一个正则表达式提取所有匹配结果(即文本片段),然后对这些片段应用第二个正则表达式进行进一步匹配**。这种方法常用于处理嵌套模式复杂文本解析,例如从模板字符串中提取数据(如引用[3]中的${xxx}示例)。以下是详细解释和实现方法。 #### 核心概念 - **匹配结果本质**:正则表达式匹配结果是文本字符串(例如,从 "${bizDate+1}" 中提取的 "bizDate+1")。这些字符串可作为新正则表达式的输入。 - **编程实现流程**: 1. **步骤1: 应用第一个正则表达式**——扫描原始文本,获取所有匹配对象匹配字符串的列表。 2. **步骤2: 应用第二个正则表达式**——对每个匹配结果(文本片段)应用第二个正则表达式,提取验证子内容。 - **为什么不能单次完成**:正则表达式匹配是贪婪非贪婪的模式搜索(如引用[3]使用非贪婪模式 `.*?`),但无法在单个表达式中直接“匹配匹配结果”。这需要迭代处理(常见于编程语言如Python、Java)。 #### 实现方法:以Python为例 以下代码演示如何先匹配引用[3]中的${xxx}模式,然后对每个匹配结果应用第二个正则表达式(例如提取变量名和操作符)。假设原始文本是SQL语句:`update demo1 set ptime=${bizDate+1} and date=${ bizDate }`。 ```python import re # 原始文本 text = "update demo1 set ptime=${bizDate+1} and date=${ bizDate }" # 步骤1: 应用第一个正则表达式匹配所有${xxx}片段(基于引用[3]的改进版) first_regex = r'\$\{\s*(\w+)\s*(([\+\-])\s*(\d+)\s*)?\}' # 匹配 ${xxx} 格式,并捕获内部内容 [^3] matches = re.finditer(first_regex, text) # 获取匹配迭代器 # 步骤2: 对每个匹配结果应用第二个正则表达式 second_regex = r'^(\w+)\s*([\+\-])?\s*(\d+)?$' # 针对内部内容的模式,提取变量名、操作符、数字 results = [] for match in matches: inner_content = match.group(0) # 完整匹配文本,如 "${bizDate+1}" clean_content = inner_content.strip('${}').strip() # 去除 ${} 符号,如 "bizDate+1" # 应用第二个正则表达式到清理后的内容 sub_match = re.match(second_regex, clean_content) if sub_match: variable = sub_match.group(1) # 提取变量名,如 "bizDate" operator = sub_match.group(2) if sub_match.group(2) else None # 提取操作符,如 "+" number = sub_match.group(3) if sub_match.group(3) else None # 提取数字,如 "1" results.append({ "full_match": inner_content, "variable": variable, "operator": operator, "number": number }) # 输出结果 print(results) # 示例输出: [{'full_match': '${bizDate+1}', 'variable': 'bizDate', 'operator': '+', 'number': '1'}, ...] ``` #### 关键点说明 1. **第一个正则表达式的作用**: - 使用 `\$\{\s*(\w+)\s*(([\+\-])\s*(\d+)\s*)?\}` 匹配 ${xxx} 格式(引用[3]的改进版)。这个表达式: - 匹配 `${` 和 `}`。 - 内部捕获变量名(`(\w+)`)和可选的操作符+数字(`(([\+\-])\s*(\d+)\s*)?`)。 - 非贪婪匹配确保处理空格(如 `${ bizDate }`)[^3]。 - 在代码中,`re.finditer` 返回所有匹配对象,便于后续处理。 2. **第二个正则表达式的作用**: - 针对清理后的内容(如 "bizDate+1"),使用 `^(\w+)\s*([\+\-])?\s*(\d+)?$` 进一步提取: - `^(\w+)`:匹配变量名开头。 - `\s*([\+\-])?`:可选的操作符(+-)。 - `\s*(\d+)?$`:可选的数字结尾。 - 这允许对匹配的子内容进行精细化解析(例如区分变量和操作)。 3. **适用场景**: - 文本模板处理(如SQL、配置文件中提取动态变量)。 - 日志解析(先匹配时间戳,再匹配其中的日期部分)。 - 数据清洗(如先匹配浮点数,再提取其整数部分——引用[1]的正则可用于第一步)[^1]。 #### 注意事项 - **性能优化**:对于大文本,避免在循环中频繁编译正则表达式(可预编译:`re.compile`)。 - **工具支持**:在支持正则的编辑器语言(如Python、JavaScript)中,此方法高效;正则表达式本身无法递归匹配。 - **局限性**:复杂嵌套(如正则匹配正则表达式)可能需要解析器而非纯正则,但上述方法是实用折中方案。 通过这种两步法,您可以高效匹配和处理其他正则表达式匹配结果[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艾恩凝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值