热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

LeetCode正则表达式匹配(10题)dp和递归方法

LeetCode正则表达式匹配author:Jingdaidate:2020.11.28题目描述(10题)给你一个字符串s和一个字符规律pÿ
LeetCode 正则表达式匹配

@author:Jingdai
@date:2020.11.28

题目描述(10题)


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

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

提示:

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

示例1

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

示例2

输入: s = "ab" p = ".*"
输出: true


思路及代码


这个题目可以用动态规划或递归来解决,思路都差不多,这里先介绍 dp 的方法,然后介绍递归的方法。

  • dp 方法
    首先要明确匹配的含义,这点很重要,这也是很多人第一次做的时候懵逼的原因(我就是)。匹配的含义是正则表达式 p 和字符串 s 以任何一种方式匹配就算匹配。比如字符串 "aa" 和 正则表达式 "a*a*" ,这里有好几种匹配方式,比如第一个 * 表示2个a,第二个 * 表示0 个 a;或者第一个 * 表示1个a,第二个 * 表示1个a。但要注意,只要有任何一种方式使正则表达式 p 和字符串 s 匹配了,那么就算是匹配了,而不用所有的方式都匹配。

    接下来看 dp,如何定义状态呢?我们这里定义 dp[i][j] 表示字符串 s 的前 i 个字母和正则表达式 p 的前 j 个字母是否匹配(注意:i,j 不是下标,后同)。

    先看初始状态,首先很容易得到 dp[0][0] 为 true(s 的前0个字母和 p 的前 0 个字母);然后当 j 等于 0 时(p的前0个字母),只有当 i 等于0时 dp[i][j] 为true,否则都为false。但是当 i 等于 0 时,dp[i][j] 需要讨论。如图,当 p"a*b*c" 时, dp[0][1] 显然为 false ,但是 dp[0][2] 为true(j = 2 的 * 代表前面有0个a); dp[0][3] 为 false, dp[0][4] 又是 true (j = 4 的 * 代表前面有0个a)。可以得出当 i 等于0时有: dp[i][j] = p.charAt(j-1) == '*' && dp[i][j-2]
    在这里插入图片描述
    下面就是 dp 的重点了,也是这个题的难点,状态转移方程。

    从最简单的开始,如图,当 s 的第 i 个元素(不是下标,后同)和 p 的第 j 个元素(不是下标,后同)相同时(或 p 的第 j 个元素是 '.' ),那么很容易得出 dp[i][j] = dp[i-1][j-1]
    在这里插入图片描述
    s 的第 i 个元素和 p 的第 j 个元素不同且 p 的第 j 个元素为 '*' 时,又分为两种情况。

    1. 如图,当 s 的第 i 个元素和 p 的第 j-1 个元素不同时,那么只有将 p 的第 j-1 个元素和第 j 个元素去掉才有可能使它们相同,故此时 * 只能代表 0 个前面的元素,此时 dp[i][j] = dp[i][j-2]

      在这里插入图片描述

    2. s 的第 i 个元素和 p 的第 j-1 个元素相同时,如图,可以发现不管 i 前有几个相同的元素,如 "cba""cbaa""cbaaa",dp[i][j] 的结果总和 dp[i-1][j] 的结果相同,相当于这里的 ij 都往前移动一位,j 移动代表 a* 少匹配一个 a ,不用动,故得到 dp[i][j] = dp[i-1][j]

      在这里插入图片描述

      但是还有特殊的情况,如图,例如当 s"a" , p 为 "aa*" 时,这时只能删除 a*,才能使 sp 匹配。此时 dp[i][j] = dp[i][j-2] 。前面说过,只要有一种方式匹配成功就算匹配,所以最后的递推式为 dp[i][j] = dp[i-1][j] || dp[i][j-2]

      在这里插入图片描述

综上,所有情况的递推式为:

  1. s 的第 i 个元素和 p 的第 j 个元素相同时:dp[i][j] = dp[i-1][j-1]
  2. s 的第 i 个元素和 p 的第 j 个元素不同且 p 的第 j 个元素为 '*' 时:
    1. s 的第 i 个元素和 p 的第 j-1 个元素不同时:dp[i][j] = dp[i][j-2]
    2. s 的第 i 个元素和 p 的第 j-1 个元素相同时:dp[i][j] = dp[i-1][j] || dp[i][j-2]
  3. 其他情况:dp[i][j] = false

代码如下。

public boolean isMatch(String s, String p) {if (s &#61;&#61; null || p &#61;&#61; null)return false;int lenS &#61; s.length();int lenP &#61; p.length();char[] sChars &#61; s.toCharArray();char[] pChars &#61; p.toCharArray();boolean[][] dp &#61; new boolean[lenS&#43;1][lenP&#43;1];for (int i &#61; 0; i <&#61; lenS; i&#43;&#43;) {for (int j &#61; 0; j <&#61; lenP; j&#43;&#43;) {if (j &#61;&#61; 0) {if (i &#61;&#61; 0) {dp[i][j] &#61; true;} // or is falsecontinue;}if (i &#61;&#61; 0) { // j !&#61; 0if (j - 3 >&#61; 0) {dp[i][j] &#61; pChars[j-1] &#61;&#61; &#39;*&#39; && dp[i][j-2];} else {dp[i][j] &#61; pChars[j-1] &#61;&#61; &#39;*&#39;; // the first cant be *}continue;}if (sChars[i-1] &#61;&#61; pChars[j-1] || pChars[j-1] &#61;&#61; &#39;.&#39;) {dp[i][j] &#61; dp[i-1][j-1];} else if (pChars[j-1] &#61;&#61; &#39;*&#39;) {if (pChars[j-2] &#61;&#61; sChars[i-1] || pChars[j-2] &#61;&#61; &#39;.&#39;) {dp[i][j] &#61; dp[i-1][j] || dp[i][j-2];} else {dp[i][j] &#61; dp[i][j-2];}}}}return dp[lenS][lenP];
}

  • 递归方法
    和 dp 相同&#xff0c;递推的式子完全一样&#xff0c;只要注意递归出口就好。递归出口就是 dp 方法中的初始化部分&#xff0c;如果上面的 dp 方法懂了&#xff0c;递归方法就非常容易看懂&#xff0c;直接上代码。

public boolean isMatch(String s, String p) {if (s &#61;&#61; null || p &#61;&#61; null) {return false;}return isMatch(s, p, s.length(), p.length());
}public boolean isMatch(String s, String p, int firstSLetters, int firstPLetters) {if (firstPLetters &#61;&#61; 0) {if (firstSLetters &#61;&#61; 0) {return true;} else {return false;}}if (firstSLetters &#61;&#61; 0) {if (p.charAt(firstPLetters-1) &#61;&#61; &#39;*&#39;) {return isMatch(s, p, firstSLetters, firstPLetters-2);} else {return false;}}if (s.charAt(firstSLetters-1) &#61;&#61; p.charAt(firstPLetters-1) || p.charAt(firstPLetters-1) &#61;&#61; &#39;.&#39;) {return isMatch(s, p, firstSLetters-1, firstPLetters-1);} else if (p.charAt(firstPLetters-1) &#61;&#61; &#39;*&#39;) {if (s.charAt(firstSLetters-1) &#61;&#61; p.charAt(firstPLetters-2) || p.charAt(firstPLetters-2) &#61;&#61; &#39;.&#39;) {return isMatch(s, p, firstSLetters-1, firstPLetters) || isMatch(s, p, firstSLetters, firstPLetters-2);} else {return isMatch(s, p, firstSLetters, firstPLetters-2);}} return false;
}


推荐阅读
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • Python爬虫中使用正则表达式的方法和注意事项
    本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
  • 本文详细介绍了Python中正则表达式和re模块的使用方法。首先解释了转义符的作用,以及如何在字符串中包含特殊字符。然后介绍了re模块的功能和常用方法。通过学习本文,读者可以掌握正则表达式的基本概念和使用技巧,进一步提高Python编程能力。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • OO第一单元自白:简单多项式导函数的设计与bug分析
    本文介绍了作者在学习OO的第一次作业中所遇到的问题及其解决方案。作者通过建立Multinomial和Monomial两个类来实现多项式和单项式,并通过append方法将单项式组合为多项式,并在此过程中合并同类项。作者还介绍了单项式和多项式的求导方法,并解释了如何利用正则表达式提取各个单项式并进行求导。同时,作者还对自己在输入合法性判断上的不足进行了bug分析,指出了自己在处理指数情况时出现的问题,并总结了被hack的原因。 ... [详细]
  • 本文介绍了Oracle存储过程的基本语法和写法示例,同时还介绍了已命名的系统异常的产生原因。 ... [详细]
author-avatar
蓝色春天绿意长
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有