LeetCode精讲题 10正则表达式匹配(动态规划)

简介: 给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和'*'的正则表达式匹配。'.'匹配任意单个字符'*'匹配零个或多个前面的那一个元素所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

题目描述



先点赞再观看、帅哥靓女养成好习惯


20200819202656554.gif


10 正则表达式匹配


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

'.'匹配任意单个字符

'*'匹配零个或多个前面的那一个元素

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


说明:

s 可能为空,且只包含从 a-z 的小写字母。

p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。


示例 1:

输入:

s = “aa”

p = "a"

输出: false

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


示例 2:

输入:

s = “aa”

p = "a*"

输出: true

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


示例 3:

输入:

s = "ab"

p = ".*"

输出: true

解释: ".*" 表示可匹配零个或多个('*')任意字符('.')


示例 4:

输入:

s = “aab”

p = “cab”

输出: true

解释: 因为 ‘*’ 表示零个或多个,这里 ‘c’ 为 0 个, ‘a’ 被重复一次。因此可以匹配字符串 “aab”。


示例 5:

输入:

s = “mississippi”

p = "mis*is*p*."

输出: false


递归(超时)



这题刚开始见到,还以为遇到原题了,因为跟剑指offer的其中一题非常像,剑指offer第52题正则表达式,只不过那题给的两个char类型的数组,当时弱弱的用递归暴力过了。


20200819190925723.png


然后一顿操作把上次递归的方法重写过来,结果超时了……

但是还是把这种递归的思路讲一下,递归主要进行匹配所有情况,主要是看当前位置两个串串能不能匹配。


需要考虑*情况可以匹配,因为*可以出现0次,1次多次。那么在遇到使用*的如果匹配了,可以通过递归实现下面三者方式


  • 它可以使用0次(相当于跟字符串下一部分匹配 a*aaaa这个第一个a*可以看成0次)
  • 也可以使用1次(在当前往后移例如a*aaaaa转成aaaa的匹配)
  • 也可以使用多次(例如a*aaa转成a*aa的匹配)


同样,如果遇到*不可以匹配,那么就使用0次就行了(b*aaaa匹配转换成aaaa匹配)


如果下一个不是*,那就考虑是否相当或者模式字符是否为.进行继续匹配或者终止就可以,在考虑一些开始结束情况就可以了,一个大概的思维导图可以看一下。


20200819193707736.png


这部分实现的代码如下:


public static boolean isMatch2(String s, String p) {
    //System.out.println(s+" "+p);
    if (p.length() == 0)// 模式串为false
    {
      if (s.length() == 0)
        return true;
      return false;
    } else if (s.length() == 0) {// 匹配串为0
      if (p.length() % 2 == 1)
        return false;
      else {
        for (int i = 1; i < p.length(); i += 2) {
          if (p.charAt(i) != '*')
            return false;
        }
        return true;
      }
    } else if (p.length() == 1) {//匹配串长度为1
      if((s.charAt(0) == p.charAt(0) || p.charAt(0) == '.')&&s.length()==1)//可以匹配
        return  true;
      else {
        return false;
      }
    } else {// 两个串串正常长度
      if(p.charAt(1)=='*')//下一个为*
      {
        if(s.charAt(0)==p.charAt(0)||p.charAt(0)=='.')//可以匹配 分别用0次 用若1次 用若干次
        {
          return isMatch(s.substring(1), p)||isMatch(s.substring(1), p.substring(2))||isMatch(s, p.substring(2));
        }
        else {//不匹配只能用0次
          return isMatch(s, p.substring(2));
        } 
      }
      else {
        if(s.charAt(0)==p.charAt(0)||p.charAt(0)=='.')
          return isMatch(s.substring(1), p.substring(1));
        else {//完全失败
          return false;
        }
      }
    }


很遗憾的超时了,不过在剑指offer是可以过的,主要遇到这种字符就会很麻烦:


isMatch("aaaaaaaaaaaaab", "a*a*a*a*a*a*a*a*a*a*c")


因为这里面匹配中的a*任意一个都可以使用若干次导致递归种类太多爆栈。嘤嘤嘤。


20200819194059117.gif


动态规划



这题正确而大众的解法当然是动态规划了,我们知道动态规划重在动态的规划方程。并且当前结果是基于父结果的。这题刚好就可以使用动态规划来解答。


我们使用我们声明一个dp[][]=new boolean[匹配串长度+1][模式串长度+1] 的二位数组用来储存结果, 其中dp[i][j]表示匹配串前i个和模式串前j个是否匹配。最终匹配串和模式串是否匹配就是返回dp[匹配串长度][模式串长度].


对于动态规划的问题,我们一般会空余出0号位放在越界等特殊情况,所以我们声明的二维数组大小长宽都大1,因为0号dp[][]表示的是空串的结果而不是一号位置串的结果。然后我们在搞动态规划题一般需要以下几步:


  • 声明dp数组,理解其含义
  • 声明一些初始情况(一般为0)
  • 找正常情况动态方程式


这里的初始我们是dp[0][0]=true表示两个空串可以匹配。


我们分析这个dp[i][j]匹配串前i个,模式串前j个是否匹配.其实这个分析和之前递归还是有点相似的:


首先如果模式串pattern第j个如果是*以下两种情况任意一种匹配成功即可。


  • 如果dp[i][j-2]==true那么dp[i][j]肯定为true,因为可以把它看成一个空串。


20200819200458348.png


  • 如果dp[i][j-2]不为true也不要紧,如果匹配串和模式串前一个字符可以匹配并且dp[i-1][j]为true,那么也可以匹配(a*a )


20200819200847204.png


如果模式串第j个不为*那么就是常规匹配了,如果当前位置字符不匹配,那么就为false,如果当前位置匹配且dp[i-1][j-1]==true那么dp[i][j]就为true:


20200819201255885.png


当然,以上所有考虑i-1的情况i不能等于0.


综上分析得到dp方程为:


if(模式串当前为*)
dp[i][j]==dp[i][j-2]||(dp[i-1][j]&&两串当前字符可以匹配)
else
dp[i][j]=dp[i-1][j-1]&&两串当前字符可以匹配


具体实现需要注意下标编号在字符串位置和dp下标的含义,具体实现的代码为:


public static boolean isMatch(String s, String p) {
  boolean dp[][]=new boolean[s.length()+1][p.length()+1];//默认为false
  dp[0][0]=true;
  for(int i=0;i<=s.length();i++)
  {
    for(int j=1;j<=p.length();j++)
    {
      if(p.charAt(j-1)=='*')//该位置为*
      {
        dp[i][j]=dp[i][j-2];//模式用了0次的看看是否能够匹配,能匹配最好,不能匹配继续
        if(!dp[i][j])//不能匹配
        {
          if(i==0) {continue;}
          else if(s.charAt(i-1)==p.charAt(j-2)||p.charAt(j-2)=='.')//可以匹配
          {
            dp[i][j]=dp[i-1][j];
          }
        }
      }
      else {//正常字符
        if(i==0){continue;}
        else if(s.charAt(i-1)==p.charAt(j-1)||p.charAt(j-1)=='.') {//这个位置可以匹配
          dp[i][j]=dp[i-1][j-1];
        }
      }       
    }
  }
  return dp[s.length()][p.length()];  
}


20200819202458627.png


结语



今天又get一个动态规划题,以前没有用动态规划的思维去想过,但是这题还是挺好的,至于一些其他的方法如果后面有时间可以继续拓展。

原创不易,最后我请你帮两件事帮忙一下:


  1. star支持一下, 您的肯定是我在平台创作的源源动力。
  2. 微信搜索「bigsai」,关注我的公众号,不仅免费送你电子书,我还会第一时间在公众号分享知识技术。加我还可拉你进力扣打卡群一起打卡LeetCode。


记得关注、咱们下次再见!



目录
相关文章
|
6月前
|
机器学习/深度学习 存储 算法
LeetCode 题目 95:从递归到动态规划实现 不同的二叉搜索树 II
LeetCode 题目 95:从递归到动态规划实现 不同的二叉搜索树 II
|
6月前
|
缓存
力扣每日一题 6/14 动态规划+数组
力扣每日一题 6/14 动态规划+数组
41 1
|
6月前
|
算法 数据挖掘 开发者
LeetCode题目55:跳跃游戏【python5种算法贪心/回溯/动态规划/优化贪心/索引哈希映射 详解】
LeetCode题目55:跳跃游戏【python5种算法贪心/回溯/动态规划/优化贪心/索引哈希映射 详解】
|
6月前
|
存储 算法 数据可视化
深入解析力扣161题:相隔为 1 的编辑距离(逐字符比较与动态规划详解)
深入解析力扣161题:相隔为 1 的编辑距离(逐字符比较与动态规划详解)
|
6月前
|
存储 算法 数据可视化
LeetCode 题目 96:从动态规划、递归到卡塔兰数实现不同的二叉搜索树
LeetCode 题目 96:从动态规划、递归到卡塔兰数实现不同的二叉搜索树
|
6月前
|
存储 算法 数据可视化
LeetCode 132题详解:使用动态规划与中心扩展法解决分割回文串 II 的最少分割次数问题
LeetCode 132题详解:使用动态规划与中心扩展法解决分割回文串 II 的最少分割次数问题
|
6月前
|
存储 算法 数据可视化
LeetCode 131题详解:高效分割回文串的递归与动态规划方法
LeetCode 131题详解:高效分割回文串的递归与动态规划方法
|
6月前
|
存储 SQL 算法
优化解码方法:记忆化搜索和空间优化动态规划的实用指南 【LeetCode 题目 91】
优化解码方法:记忆化搜索和空间优化动态规划的实用指南 【LeetCode 题目 91】
|
6月前
|
算法 索引
力扣每日一题 6/28 动态规划/数组
力扣每日一题 6/28 动态规划/数组
55 0
|
6月前
|
存储
力扣每日一题 6/19 排序+动态规划
力扣每日一题 6/19 排序+动态规划
34 0