【力扣】正则表达式匹配--回溯法解剖

news2024/11/18 3:20:11

题目:10.正则表达式匹配

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

'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

示例 1:

输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。

分析:

        一开始看这道题目,可能会被以前做过的一道题误导,就是 '*' 可以匹配0个或多个字符,nonono~,看清后才知道原来是匹配0个或多个前面的那一个元素,第一次就因为粗心大意被坑了,但这道题我还是没解出来,看了多个题解也不是很明白,最后看到了某个大佬的题解,回溯法!下面就来带大家如何完成这道困难题吧!

        首先我们可以在主函数上外包给我们的函数 back() 去完成:

return back(s, p, 0, 0);
// back()就是我们的回溯函数,s和p分别代表题目给定的字符串s、p,0、0代表目前的下标位置i和j

下面就来简单构造一下back函数的大体结构:

        我们可以先看作两个大的分支:一个是一定成功匹配,一个是不一定成功匹配

一、一定成功匹配,就是s[i] == p[j],i和j是当前下标位置,或者p[j] == '.',因为 '.' 能匹配任意一个字符。成功匹配我懂,但是为什么还有不一定成功匹配?

二、不一定成功匹配,就是s[i] != p[j] 同时 p[j] != '.',但是,我们的p[j] 可能等于 '*' 呀,所以还是有余地的,不然就直接return false吧。

所以大体的框架就是:

bool back(string s, string p, int i, int j)
    {
        // 预留递归的出口

        // 回溯处理
        if (s[i] == p[j] || p[j] == '.')
        {
            if (j + 1 < p.size() && p[j+1] == '*'){
            }else{
            }
        }else{
            if (j + 1 < p.size() && p[j+1] == '*'){
            }else{
                return false;
            }
        }
    }

现在的问题是如果是s[i] == p[j] || p[j] == '.',那就正常的递归下一个,也就是s[i+1]、p[j+1],如果是 p[j] == '*' ,那就复杂了,但这也是关键点,我们来分析一下有几种状况:

一、复制

        

        这里的 '*' 代表了复制的意思,那么问题就变成了 s[i+1] 和 p[j] 是否匹配的问题了,因为是 s[i] 已经和 p[j] 相同了,就看后面的是不是相同的字符,所以这里可以递归 back(s,p,i+1,j);

二、终止复制

         当 '*' 复制时,我们不能一直复制下去,不然最后就剩下 'b' 和 'ab' 匹配了,所以我们应该终止匹配,如何终止,可以用 s[i+2] 和 p[j+2] 匹配,所以就是 back(s,p,i+2,j+2) 了吧,错!应该是 back(s,p,i+1,j+2)!

        为什么是 i+1 ?一开始我也想了很久,下面是我的个人观点:因为我们不确定是否需要终止复制,所以是在复制之后才开始终止复制,而不是一开始就终止复制,所以是执行了back(s,p,i+1,j) 再终止复制, 此时就相当于 back(s,p,i+1+1,j+2),也就是 s[i+2] 和 p[j+2] 的比较,如果是i+2,就是 s[i+3] 和 p[j+2] 的比较了。(如果有不同意见的可以再留言区指证)

三、越过

        当 '*' 是消除的意思时,我们就需要越过 '*' ,把a当作0个去掉,这时候就是  s[i] 和 p[j+2] 匹配比较了,也就是 back(s,p,i,j+2);

按照上面的逻辑,我们可以推出代码:

    bool back(string s, string p, int i, int j)
    {
        // 出口
 
        // 回溯处理
        if (s[i] == p[j] || p[j] == '.')
        {
            // 复制+复制终止+越过
            if (j + 1 < p.size() && p[j+1] == '*'){
                return back(s, p, i+1, j) || back(s, p, i+1, j+2) || back(s, p, i, j+2);
            }else{
                return back(s, p, i+1, j+1);
            }
        }else{
            // 还有机会,p[j+1]是'*',可能是越过
            if (j + 1 < p.size() && p[j+1] == '*')
                return back(s, p, i, j+2);
            else
                return false;
        }
    }

接下来就是怎么推出递归:

        我们需要在题目中添加全局变量sn、pn来记录字符串s、p的长度,用size()也可以,当需要匹配的字符串 s 的 i 下标到 sn 时,就说明 sn 已经匹配完了,所以要判断 j 是否也匹配到结尾了,这时就有两种情况:一、j 正常到 pn,二、j 未到 pn。

        如果 j 未到 pn 就返回错误了吗?未必,看下面的情况:

        如果后面的 'x*x*' 的格式,就等于越过的意思,所以我们在 return 时要再次递归越过式,最后,如果 i == sn 不成立,但 j == pn 成立了,那说明字符串 p 已经结束而 s 未结束,所以是false的。下面来看看代码:

class Solution {
    int sn;
    int pn;
public:
    bool back(string s, string p, int i, int j)
    {
        // 出口
        if (i == sn)
            return j == pn || (j+1<pn&&p[j+1]=='*'&& back(s,p,i,j+2));
        else if (j == pn)
            return false;

        // 回溯处理
        if (s[i] == p[j] || p[j] == '.')
        {
            // 复制+复制终止+越过
            if (j + 1 < p.size() && p[j+1] == '*'){
                return back(s, p, i+1, j) || back(s, p, i+1, j+2) || back(s, p, i, j+2);
            }else{
                return back(s, p, i+1, j+1);
            }
        }else{
            // 还有机会,p[j+1]是'*',可能是越过
            if (j + 1 < p.size() && p[j+1] == '*')
                return back(s, p, i, j+2);
            else
                return false;
        }
    }

    bool isMatch(string s, string p) {
        sn = s.size(), pn = p.size();
        return back(s, p, 0, 0);
    }
};

按道理来说,到这里已经结束了,but!力扣跟新了例子,发现会超时,那怎么办呢?

        超出时间范围,很明显我们做了许多重复的操作,使其开销过大,所以我们需要定义一个二维数组或者向量来记录我们所到的地方是否正确,如果有错误,就说明已经判断过这个地方并且赋值了false,就可以上来就 return 掉,可以节省很多的时间及资源。

        我们可以创建一个 dp[][] 长度需要预留多一位,可以有效避免访问出错,随后就把原先该得到的值先存到 dp[i][j] 当中,再 return dp[i][j], 所以改良后的代码如下:

class Solution {
    int sn;
    int pn;
    vector<vector<bool>> memo;
public:
    bool back(string s, string p, int i, int j)
    {
        // 记忆化减枝
        if (memo[i][j] == false)
            return false;
        // 出口
        if (i == sn){
            memo[i][j] = j == pn || (j+1<pn&&p[j+1]=='*'&& back(s,p,i,j+2));
            return memo[i][j];
        }
        else if (j == pn){
            memo[i][j] = false;
            return memo[i][j];
        }

        // 回溯处理
        if (s[i] == p[j] || p[j] == '.')
        {
            // 复制+复制终止+越过
            if (j + 1 < p.size() && p[j+1] == '*'){
                memo[i][j] = back(s, p, i+1, j) || back(s, p, i+1, j+2) || back(s, p, i, j+2);
                return memo[i][j];
            }else{
                memo[i][j] = back(s, p, i+1, j+1);
                return memo[i][j];
            }
        }else{
            // 还有机会,p[j+1]是'*',可能是越过
            if (j + 1 < p.size() && p[j+1] == '*'){
                memo[i][j] = back(s, p, i, j+2);
                return memo[i][j];                
            }else{
                memo[i][j] = false;
                return memo[i][j];                
            }
        }
    }

    bool isMatch(string s, string p) {
        sn = s.size(), pn = p.size();
        memo = vector<vector<bool>>(sn+1,vector<bool>(pn+1,true));
        return back(s, p, 0, 0);
    }
};

执行后就可以通过啦!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1047.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

maven配置阿里镜像,解决IDEA配置maven恢复默认配置问题

文章目录1.三个IDEA配置说明2.拷贝与修改settings.xml2.1 找到IDEA的mave配置文件settings.xml位置2.2 拷贝IDEA的settings.xml到.m2目录下2.3 打开settings.xml&#xff0c;配置本地库LocalRepository的路径2.4 删除默认镜像配置&#xff0c;配置阿里镜像2.5 保存文件3.验证是…

面试时,MySQL这些基础知识你回答的出来吗?

目录 【一】前言 【二】MySQL的并发控制 【三】数据库的事务 【四】隔离级别 【五】死锁 【六】存储引擎 6.1 InnoDB存储引擎 6.2 MyISAM存储引擎 【七】总结 【一】前言 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB公司开发&#xff0c;属于Oracle…

一文就读懂RPC远程调用核心原理

rpc核心原理 什么是rpc&#xff1f; rpc的全称是Remote Procedure Call&#xff0c;即远程过程调用&#xff0c;是分布式系统的常用通信方法。 Remote&#xff0c;简单来说的话就是两个不同的服务之间&#xff0c;两个服务肯定是两个不同的进程。因此&#xff0c;我们就从跨进…

【微服务】微服务万字实战,带你了解工程原理

微服务实战1、前期准备1.1 技术选型1.2 模块设计1.3 微服务调用2、创建父工程3、创建基础模块3.1 导入依赖3.2 创建实体类4、创建用户微服务4.1 创建shop-user模块4.2 用户微服务启动类4.3 创建配置文件5、创建商品微服务5.1 创建shop_product模块5.2 商品微服务启动类5.3 创建…

刷题日记【第六篇】-笔试必刷题【最近公共祖先+求最大连续bit数+二进制插入+查找组成一个偶数最接近的两个素数】

目录 选择题模块 1.下面哪个标识符是合法的&#xff1f;&#xff08;D&#xff09; 2.以下描述正确的是&#xff08;B&#xff09; 3.下列程序的运行结果&#xff08;B&#xff09; 4.下列关于容器集合类的说法正确的是&#xff1f;&#xff08;C&#xff09; 5.ArrayList…

【MySQL进阶】深入理解InnoDB记录结构

【MySQL进阶】深入理解InnoDB记录结构 参考资料&#xff1a;《MySQL是怎么运行的&#xff1a;从根儿上理解MySQL》。 前言&#xff1a; 我们一般使用的MySQL关系型数据库&#xff0c;更是经典中的经典&#xff0c;虽说MySQL已经非常成熟&#xff0c;但对于MySQL的掌握程度&a…

腾讯Java888道高频面试真题笔记+Java面试宝典

这多半年你是否达到了你年初定的目标&#xff0c;今年企业招聘要求也是更加的严格&#xff0c;对于低学历,以及技术实力不过关的更是雪上加霜。也是由于种种缘由&#xff0c;从5月开始就一直有粉丝私信要博主整理一些干货来帮助他们提升下自己&#xff0c;为了响应粉丝要求&…

Non-Autoregressive Coarse-to-Fine Video Captioning【论文阅读】

Non-Autoregressive Coarse-to-Fine Video Captioning 发表&#xff1a;AAAI 2021idea&#xff1a;&#xff08;1&#xff09;针对推理阶段不能并行&#xff0c;推理效率低的问题使用一种双向解码&#xff08;在bert中不使用sequence mask&#xff09;。&#xff08;2&#xf…

基于QD求解法的二分类SVM仿真

目录 1.算法概述 2.部分程序 3.算法部分仿真结果图 4.完整程序获取 CSDN用户&#xff1a;我爱C编程 CSDN主页&#xff1a;https://blog.csdn.net/hlayumi1234567?typeblog 擅长技术&#xff1a;智能优化&#xff0c;路径规划&#xff0c;通信信号&#xff0c;图像处理&…

【数据结构与算法分析】0基础带你学数据结构与算法分析06--树(TREE)

目录 前言 树的属性 树的实现 树的遍历与应用 深度有限遍历 (DFS) 广度优先遍历 (BFS) Not all roots are buried down in the ground, some are at the top of a tree. — Jinvirle 前言 Tree 是一些结点的集合&#xff0c;这个集合可以是空集&#xff1b;若不是空集…

【模型训练】YOLOv7行人检测

YOLOv7行人检测 1、YOLOv7算法行人检测模型训练2、YOLOv7模型模型评估3、模型和数据集下载1、本项目采用YOLOv7算法实现对行人的检测和识别,在一万多张行人检测数据集中训练得到,我们训练了YOLOv7模型,经评估我们得出了各个模型的评价指标; 2、目标类别数:1 ;类别名:pers…

笔试强训第15天(手套+ 查找输入整数二进制中1的个数)

选择 C barfoob_bar new B 会先创建一个B类对象&#xff0c;B类对象的构造需要调用B的构造函数&#xff0c;从而调用A的构造函数。A的构造函数中调用了 bar()函数&#xff0c;该函数虽然重写了&#xff0c;但这里不构成多态调用。因为虚表中的函数指针是在构造函数的初始化列表…

爱上源码,重学Spring IoC深入

回答&#xff1a; 我们为什么要学习源码&#xff1f; 1、知其然知其所以然 2、站在巨人的肩膀上&#xff0c;提高自己的编码水平 3、应付面试1.1 Spring源码阅读小技巧 1、类层次藏得太深&#xff0c;不要一个类一个类的去看&#xff0c;遇到方法该进就大胆的进 2、更不要一行…

【3D目标检测】SECOND: Sparsely Embedded Convolutional Detection

目录概述细节网络结构稀疏卷积方向分类损失函数概述 首先&#xff0c;本文是基于点云&#xff0c;并且将点云处理成体素的3D目标检测网络&#xff0c;提出的SECOND可以看做是VoxelNet的升级版。 提出动机与贡献 VoxelNet计算量比较大&#xff0c;速度比较慢&#xff08;训练和…

第二节:数据类型与变量【java】

目录 &#x1f4c3;前言 &#x1f4d7;1.数据类型 &#x1f4d5;2. 变量 2.1 变量概念 2.2 语法格式 &#x1f4d9;3.整型变量 3.1 整型变量 3.2 长整型变量 3.3 短整型变量 3.4 字节型变量 &#x1f4d8;4.浮点型变量 4.1 双精度浮点型 4.2 单精度浮点型 &#…

[SpringBoot] AOP-AspectJ 切面技术

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

python的编译器与解释器

作者介绍&#xff1a; &#x1f425;作者&#xff1a;小刘在C站 &#x1f446;每天分享课堂笔记&#xff0c;一起努力&#xff0c;共赴美好人生 &#x1f341;夕阳下&#xff0c;是最美的绽放 目录 一.为什么会有编译器和解释器 二.编译器和解释器的区别 三.python解释器种类…

RK3399应用开发 | 移植libdrm到rk3399开发板(2.4.113)

一、下载源码 下载地址:https://dri.freedesktop.org/libdrm/。 这里我下载最新的2.4.113版本: wget https://dri.freedesktop.org/libdrm/libdrm-2.4.113.tar.xz解压: xz -d libdrm-2.4.113.tar.xz tar -xf libdrm-2.4.113.tar二、编译环境安装 1. 更新python ubuntu安…

CalBioreagents 艾美捷重组BCOADC-E2蛋白说明书

艾美捷CalBioreagents 重组BCOADC-E2蛋白英文说明&#xff1a; PRODUCT DESCRIPTION: Branched Chain 2-Oxo-Acid Dehydrogenase Complex E2 protein, recombinant. CLINICAL INDICATION: Primary biliary cirrhosis CATALOG NUMBER: A268 SOURCE: Recombinant protein ex…

《CTF攻防世界web题》之茶壶我爱你(2)

前言 &#x1f340;作者简介&#xff1a;被吉师散养、喜欢前端、学过后端、练过CTF、玩过DOS、不喜欢java的不知名学生。 &#x1f341;个人主页&#xff1a;被吉师散养的职业混子 &#x1fad2;文章目的&#xff1a;记录唯几我能做上的题 &#x1f342;相应专栏&#xff1a;CT…