Leetcode算法系列| 10. 正则表达式匹配

news2025/1/20 19:22:22

目录

  • 1.题目
  • 2.题解
    • C# 解法一:分段匹配法
    • C# 解法二:回溯法
    • C# 解法三:动态规划

1.题目

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
1.‘.’ 匹配任意单个字符
2.‘.’ 匹配任意单个字符
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

  • 示例1:
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
  • 示例 2:
输入:s = "aa", p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
  • 示例3:
输入:s = "ab", p = ".*"
输出:true
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。
  • 提示:
    • 1 <= s.length <= 20
    • 1 <= p.length <= 20
    • s 只包含从 a-z 的小写字母。
    • p 只包含从 a-z 的小写字母,以及字符 . 和 *。
    • 保证每次出现字符 * 时,前面都匹配到有效的字符

2.题解

  • 映入脑海的第一个想法是将数字转换为字符串,并检查字符串是否为回文。但是,这需要额外的非常量空间来创建问题描述中所不允许的字符串。
  • 第二个想法是将数字本身反转,然后将反转后的数字与原始数字进行比较,如果它们是相同的,那么这个数字就是回文。
  • 但是,如果反转后的数字大于 int.MAX\text{int.MAX}int.MAX,我们将遇到整数溢出问题。
  • 按照第二个想法,为了避免数字反转可能导致的溢出问题,为什么不考虑只反转 int\text{int}int 数字的一半?毕竟,如果该数字是回文,其后半部分反转后应该与原始数字的前半部分相同。
    例如,输入 1221,我们可以将数字 “1221” 的后半部分从 “21” 反转为 “12”,并将其与前半部分 “12” 进行比较,因为二者相同,我们得知数字 1221 是回文。

C# 解法一:分段匹配法

  • 根据星号的位置将p切割为多个 尾星串 与 至多一个 无星串,然后从p头到p尾求M值。求解p的某段的M值时,需要根据上一段的M值来依次求解;若上一段M不包含任何值,则匹配失败。若p从头到尾走完了, 则判断最终的M中是否包含了 s.length-1 这个值, 若包含了,则s与p是匹配的。
public class Solution {
 public bool IsMatch(string s, string p)
       {
           List<int> M1 = new List<int>() { -1 };
           while (p.Length > 0 && M1.Count > 0)
           {
               List<int> M2 = new List<int>() { };
               int pStarIndex = p.IndexOf("*");
               foreach (int m1 in M1)
               {
                   string sRight = s.Substring(m1 + 1);
                   if (pStarIndex == -1)
                   {
                       var result = IsMatchNoStar(sRight, p);
                       //无星说明是p的最后一组,若匹配成功则s与p匹配,直接返回true;否则直接continue
                       if (result) return true;
                       continue;
                   }
                   //有星,需要 找到 p[0,starIndex] 与 s 的匹配点
                   //先判断p[0,starIndex-2]的字符与s的一一对应 
                   int mNow = -1;
                   bool isMatchBeforeStar = true;
                   for (int i = 0; i < pStarIndex - 1; i++)
                   {
                       if (i >= sRight.Length)
                       {
                           isMatchBeforeStar = false; break;
                       }
                       mNow = i;
                       if (!IsMatchChar(sRight[i], p[i]))
                       {
                           isMatchBeforeStar = false; break;
                       }
                   }
                   if (!isMatchBeforeStar) continue;
                   //因为*可以表示0个,所以先将mNow添加到 匹配点集
                   M2.Add(mNow + (m1 + 1));
                   mNow++;
                   //再看p[startIndex-1] 与 s的匹配点,找出所有的匹配点 
                   while (mNow < sRight.Length && IsMatchChar(sRight[mNow], p[pStarIndex - 1]))
                   {
                       M2.Add(mNow + (m1 + 1));
                       mNow++;
                   }
               }
               //将startIndex以及之前的串给舍弃,留下startIndex+1以及之后的串
               p = p.Substring(pStarIndex + 1);
               //更新M1 
               M1 = M2;
           }
           return M1.Contains(s.Length - 1);
       }

       public bool IsMatchNoStar(string s, string p)
       {
           if (s.Length != p.Length)
           {
               return false;
           }
           for (int i = 0; i < s.Length; i++)
           {
               if (!IsMatchChar(s[i], p[i]))
               {
                   return false;
               }
           }
           return true;
       }

       public bool IsMatchChar(char sc, char pc)
       {
           return ((pc == '.') || (sc == pc));
       }
}

可以牺牲部分可读性 来提高效率, 优化后的代码:

public class Solution {
      public bool IsMatch(string s, string p)
    {
        List<int> M1 = new List<int>() { -1 };
        List<int> M2 = new List<int>() { };
        int pStart = 0;
        while (p.Length - pStart > 0 && M1.Count > 0)
        {
            int pStarIndex = p.IndexOf("*", pStart);
            foreach (int m1 in M1)
            {
                int sStart = m1 + 1;
                int sRightLen = s.Length - sStart;
                if (pStarIndex == -1)
                {
                    //IsMatchNoStar
                    var result = true;
                    if (s.Length - sStart != p.Length - pStart) result = false;
                    else
                    {
                        for (int i = 0; i < s.Length - sStart; i++)
                        {
                            if (!((s[sStart + i] == p[pStart + i]) || (p[pStart + i] == '.')))
                            {
                                result = false;
                                break;
                            }
                        }
                    }
                    if (result) return true;
                    continue;
                }
                int mNow = -1;
                bool isMatchBeforeStar = true;
                int pLenBeforeStar = pStarIndex - pStart - 1;
                for (int i = 0; i < pLenBeforeStar; i++)
                {
                    if (i >= sRightLen)
                    {
                        isMatchBeforeStar = false; break;
                    }
                    mNow = i;
                    if (!((s[sStart + i] == p[pStart + i]) || (p[pStart + i] == '.')))
                    {
                        isMatchBeforeStar = false; break;
                    }
                }
                if (!isMatchBeforeStar) continue;
                M2.Add(sStart + mNow++);
                int pCharIndexBeforeStar = pStarIndex - 1;
                while (mNow < sRightLen && ((s[sStart + mNow] == p[pCharIndexBeforeStar]) || (p[pCharIndexBeforeStar] == '.')))
                    M2.Add(sStart + mNow++);
            }
            pStart = pStarIndex + 1;
            var tempMs = M1; M1 = M2; M2 = tempMs;
            M2.Clear();
        }
        return M1.Contains(s.Length - 1);
    }
}

1

  • 时间复杂度:O( pLen * sLen^2 )
    • 最坏的情况下,s全是同一个字母,p是同一个字母加星号,如 s=“aaaaaaa”,p= “aaa*” 。此时外层while以及找星的位置可以看作pLen,内层foreach每次都是对s的遍历,执行次数为sLen,再内层的 for与while加起来 又是对s的遍历,复杂度大概为 O( pLen * sLen^2 )
  • 空间复杂度:O( pLen * sLen )
    • 我第二层循环里面存在常数数量的变量定义,故为 O(pLen*sLen)

C# 解法二:回溯法

  • 回溯法解体的思路与分段匹配法类似,但使用递归后,只需要非常少的代码量。

  • 以p为主串,从左向右去匹配s,匹配成功的部分都去掉,也就是说 若最终s与p都变成了空串,则匹配成功。

  • 代码结构也很简单,首先是出口,然后是递归分支。

  • 出口EXIT:p变成空串时,若s也变成了空串,则匹配成功,否则匹配失败。

  • 分支A:p[1]为星号,直接去掉p的前两位,并递归。如 s=“b”,p=“a*b”.

  • 分支B:p[1]为星号时,若s第一位与p第一位匹配,去掉s第一位 , 并递归,如 “s=aab”,p=“ab"。否则匹配失败,如 s=“bba”,p="ab”.

  • 分支C:p[1]不为星号时,若s与p第一位匹配成功, 则都去掉第一位,并递归,如 s=“aab”,p=“aab*”. 否则匹配失败,如 s=“bab”, p=“aab*” .

  • 其中,当p[1]为星号时,分支A与分支B是【或】的关系,只要有一条成功,则匹配成功; 当p[1]不为星号时,就走C。

public class Solution {
   public bool IsMatch(string s, string p)
   {
       //出口,EXIT
       if (string.IsNullOrEmpty(p)) return string.IsNullOrEmpty(s); //Exit

        bool first_match = (   
           !string.IsNullOrEmpty(s) &&
           (p[0] == s[0] || p[0] == '.')
       );
      
       if (p.Length >= 2 && p[1] == '*')
           return (IsMatch(s, p.Substring(2)) ||       // A
                   (first_match && IsMatch(s.Substring(1), p))); //B
       else 
           return first_match && IsMatch(s.Substring(1), p.Substring(1)); // C
   }
}

可以使用下标指针来代替SubString,从而明显的提高代码效率。 优化后的代码:

public class Solution {
   public bool IsMatch(string s, string p) { return IsMatch1(s, 0, p, 0); }

   public bool IsMatch1(string s, int sStart, string p, int pStart)
   {
       if (pStart == p.Length) return sStart == s.Length; //Exit
       bool first_match = (
           sStart < s.Length &&
           (p[pStart] == s[sStart] || p[pStart] == '.')
       );
       if (p.Length - pStart >= 2 && p[pStart + 1] == '*')
           return (IsMatch1(s, sStart, p, pStart + 2) ||       // A
                   (first_match && IsMatch1(s, sStart + 1, p, pStart))); //B
       else
           return first_match && IsMatch1(s, sStart + 1, p, pStart + 1); // C
   }
}

2

  • 时间复杂度:O( (sLen+pLen) 2^(sLen+pLen/2) )*
  • 空间复杂度:O( (sLen+pLen) 2^(sLen+pLen/2) )*

C# 解法三:动态规划

public class Solution {
    public bool IsMatch(string s, string p)
    {
        bool[,] dp = new bool[s.Length + 1, p.Length + 1];
        dp[s.Length, p.Length] = true;
        for (int i = s.Length; i >= 0; i--)
        {
            for (int j = p.Length - 1; j >= 0; j--)
            {
                bool first_match = (i < s.Length && (p[j] == s[i] || p[j] == '.'));
                if (j + 1 < p.Length && p[j + 1] == '*')
                {
                    dp[i, j] = dp[i, j + 2]    //A
                        || first_match && dp[i + 1, j]; //B
                }
                else
                {
                    dp[i, j] = first_match && dp[i + 1, j + 1]; // C
                }
            }
        }
        return dp[0, 0];
    }
}

3

  • 时间复杂度:O(sLen*pLen)
    • 最table中每个值会被计算一次,不会重复计算,而每个格子的计算时间可认为是O(1),所以总时间复杂度为O(sLen*pLen)
  • 空间复杂度:O(sLen*pLen)
    • able空间复杂度为 O(sLen*pLen).

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

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

相关文章

YOLOv8 上手体验

Yooooooo&#x1f387; &#x1f96a;环境搭建⚡注意&#x1f4a1;CUDAPyTorch&#x1f4a1;ultralytics &#x1f9aa;食用&#x1f4a1;cmd&#x1f4a1;Python &#x1f372;导出官方模型到本地 &#x1f96a;环境搭建 ⚡注意 Python>3.8 PyTorch>1.8 &#x1f4a1;C…

2023-12-30 AIGC-LangChain指南-打造LLM的垂域AI框架

摘要: 2023-12-30 AIGC-LangChain指南-打造LLM的垂域AI框架 LangChain指南-打造LLM的垂域AI框架 CHATGPT以来&#xff0c;Langchain 可能是目前在 AI 领域中最热门的事物之一&#xff0c;仅次于向量数据库。 它是一个框架&#xff0c;用于在大型语言模型上开发应用程序&#…

数据库原理与应用快速复习(期末急救)

文章目录 第一章数据库系统概述数据、数据库、数据库管理系统、数据定义、数据组织、存储和管理、数据操纵功能、数据库系统的构成数据管理功能、数据库管理的3个阶段以及特点数据库的特点、共享、独立、DBMS数据控制功能数据库的特点 数据模型两类数据模型、逻辑模型主要包括什…

每日一题——LeetCode961

方法一 排序法&#xff1a; 2*n长度的数组里面有一个元素重复了n次&#xff0c;那么将数组排序&#xff0c;求出排序后数组的中间值&#xff08;因为长度是偶数&#xff0c;没有刚好的中间值&#xff0c;默认求的中间值是偏左边的那个&#xff09;那么共有三种情况&#xff1a;…

【JavaEE进阶】 @RequestMapping注解

文章目录 &#x1f384;什么是RequestMapping 注解&#x1f333;RequestMapping 使⽤&#x1f332;RequestMapping 是GET还是POST请求&#xff1f;&#x1f6a9;使用Postman构造POST请求 ⭕总结 &#x1f384;什么是RequestMapping 注解 在Spring MVC 中使⽤ RequestMapping 来…

EasyNTS端口穿透服务新版本发布 0.8.7 增加隧道流量总数记录,可以知晓设备哪个端口耗费流量了

EasyNTS上云平台可通过远程访问内网应用&#xff0c;包含网络桥接、云端运维、视频直播等功能&#xff0c;极大地解决了现场无固定IP、端口不开放、系统权限不开放等问题。平台可提供一站式上云服务&#xff0c;提供直播上云、设备上云、业务上云、运维上云服务&#xff0c;承上…

m3u8网络视频文件下载方法

在windows下&#xff0c;使用命令行cmd的命令下载m3u8视频文件并保存为mp4文件。 1.下载ffmpeg&#xff0c;访问FFmpeg官方网站&#xff1a;https://www.ffmpeg.org/进行下载 ffmpeg下载&#xff0c;安装&#xff0c;操作说明 https://blog.csdn.net/m0_53157282/article/det…

用通俗易懂的方式讲解大模型:使用 FastChat 部署 LLM 的体验太爽了

之前介绍了Langchain-Chatchat 项目的部署&#xff0c;该项目底层改用了 FastChat 来提供 LLM(大语言模型)的 API 服务。 出于好奇又研究了一下 FastChat&#xff0c;发现它的功能很强大&#xff0c;可以用来部署市面上大部分的 LLM 模型&#xff0c;可以将 LLM 部署为带有标准…

精品Nodejs实现的校园疫情防控管理系统的设计与实现健康打卡

《[含文档PPT源码等]精品Nodejs实现的校园疫情防控管理系统的设计与实现[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; 操作系统&#xff1a;Windows 10、Windows 7、Win…

uniapp中uview组件库丰富的Calendar 日历用法

目录 基本使用 #日历模式 #单个日期模式 #多个日期模式 #日期范围模式 #自定义主题颜色 #自定义文案 #日期最大范围 #是否显示农历 #默认日期 基本使用 通过show绑定一个布尔变量用于打开或收起日历弹窗。通过mode参数指定选择日期模式&#xff0c;包含单选/多选/范围…

gitlab 11.11.8的备份与恢复及500错误的修复

gitlab已经集成了非常方便的备份和恢复命令&#xff0c;只要我们执行这些命令就能完成gitlab的备份与恢复了。 我想gitlab备份与恢复的目的无非就是将已经运行了很久的旧的gitlab服务&#xff0c;迁移到新的服务器上。如果你旧的gitlab上项目很少&#xff0c;就需要考虑迁移服…

基于ElementUI二次封装el-table与el-pagination分页组件[实际项目使用]

效果&#xff1a; 二次封装el-table组件 <template><div><!-- showHeader:是否显示头部size:表格的大小height:表格的高度isStripe:表格是否为斑马纹类型tableData:表格数据源isBorder:是否表格边框handleSelectionChange:行选中&#xff0c;多选内容发生变化回…

基于虚拟机ubuntu的linux和shell脚本的学习,以及SSH远程登陆实战

简介 特点 是一款操作系统,跟windows,macos一样,有下面的特点 简单和高效,一切皆文件,所有配置都通过修改文件解决,不需要繁琐的权限和设置 权限高,把所有细节都交给用户,可完全自定义 安全,所有程序只有自己执行才会启动 分类 1、debian系主要有Debian&#xff0c;Ubun…

yolov8实战第四天——yolov8图像分类 ResNet50图像分类(保姆式教程)

yolov8实战第一天——yolov8部署并训练自己的数据集&#xff08;保姆式教程&#xff09;_yolov8训练自己的数据集-CSDN博客在前几天&#xff0c;我们使用yolov8进行了部署&#xff0c;并在目标检测方向上进行自己数据集的训练与测试&#xff0c;今天我们训练下yolov8的图像分类…

PHP序列化总结1--序列化和反序列化的基础知识

序列化和反序列化的作用 1.序列化&#xff1a;将对象转化成数组或者字符串的形式 2.反序列化&#xff1a;将数组或字符串的形式转化为对象 为什么要进行序列化 这种数据形式中间会有很多空格&#xff0c;不同人有不同的书写情况&#xff0c;可能还会出现换行的情况 为此为了…

C# Image Caption

目录 介绍 效果 模型 decoder_fc_nsc.onnx encoder.onnx 项目 代码 下载 C# Image Caption 介绍 地址&#xff1a;https://github.com/ruotianluo/ImageCaptioning.pytorch I decide to sync up this repo and self-critical.pytorch. (The old master is in old ma…

07-项目打包 React Hooks

项目打包 项目打包是为了把整个项目都打包成最纯粹的js&#xff0c;让浏览器可以直接执行 打包命令已经在package.json里面定义好了 运行命令&#xff1a;npm run build&#xff0c;执行时间取决于第三方插件的数量以及电脑配置 打包完之后再build文件夹下&#xff0c;这个…

Self-attention学习笔记(Self Attention、multi-head self attention)

李宏毅机器学习Transformer Self Attention学习笔记记录一下几个方面的内容 1、Self Attention解决了什么问题2、Self Attention 的实现方法以及网络结构Multi-head Self Attentionpositional encoding 3、Self Attention 方法的应用4、Self Attention 与CNN以及RNN对比 1、Se…

STM32移植LVGL图形库

1、问题1&#xff1a;中文字符keil编译错误 解决方法&#xff1a;在KEIL中Options for Target Flash -> C/C -> Misc Controls添加“--localeenglish”。 问题2&#xff1a;LVGL中显示中文字符 使用 LVGL 官方的在线字体转换工具&#xff1a; Online font converter -…

Python面向对象编程 —— 类和异常处理

​ &#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 &#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 1. 类 1.1 类的定义 1.2 类变量和实例变量 1.3 类的继承 2. 异常处理 2.1类型异常 2.…