【LeetCode】P10 正则表达式匹配

P10 正则表达式匹配

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

题目描述

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

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

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

说明

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

示例:

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

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

输入:
s = " ab "
p = " .* "
输出: true
解释: " .* " 表示可匹配零个或多个( ’ * ’ )任意字符( ’ . ’ )。

输入:
s = " aab "
p = " c*a*b "
输出: true
解释: 因为 ’ * ’ 表示零个或多个,这里 ’ c ’ 为 0 个, ’ a ’ 被重复一次。因此可以匹配字符串 " aab "。

输入:
s = " mississippi "
p = " mis*is*p*. "
输出: false

题解

方法一:动态规划

链接:动态规划.

思路

  • ①将原问题分解为子问题

子问题:“字符串 s s s 的前 i i i 个字符形成的子串与字符规律 p p p 的前 j j j 个字符形成的子串是否能够匹配”。记 s s s p p p 的长度分别为 m m m n n n,那么共有 m ⋅ n m\cdot n mn 个子问题,原问题的解即为最后一个子问题的解,即 s s s m m m 个字符形成的子串与 p p p n n n 个字符形成的子串是否能够匹配。

  • ②确定状态

每个子问题中,与两个变量有关: i i i j j j,所以变量 i i i j j j 共同组成状态,定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 s s s i i i 个字符形成的子串与 p p p j j j 个字符形成的子串是否能够匹配, d p [ i ] [ j ] dp[i][j] dp[i][j] 即为状态 i i i j j j 对应的值。

  • ③确定一些边界状态(初始状态)的值

显然有, s s s 0 0 0 个字符形成的子串与 p p p 0 0 0 个字符形成的子串都是空串,所以它们是匹配的, s s s i i i 个字符形成的非空串与 p p p 0 0 0 个字符形成的空串是一定不匹配的,而需要注意的是 s s s 0 0 0 个字符形成的空串与 p p p j j j 个字符形成的非空串是可能会匹配的,如 “ ” “” “ a ∗ ” “a*” a,所以这些状态的值不能直接作为边界状态的值给出。
d p [ i ] [ j ] = { t r u e i = 0 , j = 0 f a l s e j = 0 , 0 < i ⩽ m dp[i][j] = \begin{cases} true &i=0,j=0 \\ false &j=0,0< i\leqslant m \end{cases} dp[i][j]={truefalsei=0j=0j=00<im

  • ④确定状态转移方程

    • p [ j − 1 ] p[j-1] p[j1] 为字母或者 ‘ . ’ ‘.’ . ,那么在 s s s 中必须要匹配一个字符 d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] m a t c h ( s [ i − 1 ] , p [ j − 1 ] ) f a l s e o t h e r w i s e dp[i][j] = \begin{cases} dp[i-1][j-1] & match(s[i-1],p[j-1]) \\ false & otherwise \end{cases} dp[i][j]={dp[i1][j1]falsematch(s[i1],p[j1])otherwise
      m a t c h ( x , y ) match(x,y) match(x,y):判断字符 x x x 与 字符 y y y 是否匹配,只有字符 x x x 与字符 y y y 相同或者字符 y y y ‘ . ’ ‘.’ . 才会匹配。若 s [ i − 1 ] s[i-1] s[i1] p [ j − 1 ] p[j-1] p[j1] 可以匹配,那么 s s s 的前 i i i 个字符与 p p p 的前 j j j 个字符的匹配结果取决于 s s s 的前 i − 1 i-1 i1 个字符与 p p p 的前 j − 1 j-1 j1 个字符的匹配结果,否则, s s s 的前 i i i 个字符与 p p p 的前 j j j 个字符必定不匹配。

    • p [ j − 1 ] p[j-1] p[j1] ‘ ∗ ’ ‘*’ p [ j − 2 ] p[j-2] p[j2] p [ j − 1 ] p[j-1] p[j1] 组成 x ∗ x* x 组合( x x x 为字母或者 ‘ . ’ ‘.’ .

      • p [ j − 2 ] p[j-2] p[j2] s [ i − 1 ] s[i-1] s[i1] 不能匹配,那么就意味着组合 x ∗ x* x 匹配 s s s 中的 0 0 0 个字符,该组合将被直接丢弃。
      • p [ j − 2 ] p[j-2] p[j2] s [ i − 1 ] s[i-1] s[i1] 能匹配,那么可能会有两种情况
        • 匹配字符 s [ i − 1 ] s[i-1] s[i1],组合 x ∗ x* x 继续保留
        • 匹配 s s s 中的 0 0 0 个字符,该组合直接被丢弃

d p [ i ] [ j ] = { d p [ i − 1 ] [ j ]   o r   d p [ i ] [ j − 2 ] m a t c h ( s [ i − 1 ] , p [ j − 2 ] ) d p [ i ] [ j − 2 ] o t h e r w i s e dp[i][j] = \begin{cases} dp[i-1][j]\ or\ dp[i][j-2] &match(s[i-1],p[j-2]) \\ dp[i][j-2] &otherwise \end{cases} dp[i][j]={dp[i1][j] or dp[i][j2]dp[i][j2]match(s[i1],p[j2])otherwise

由以上的讨论,我们可以写出最终的状态转移方程:
d p [ i ] [ j ] = { = { d p [ i − 1 ] [ j ]   o r   d p [ i ] [ j − 2 ] m a t c h ( s [ i − 1 ] , p [ j − 2 ] ) d p [ i ] [ j − 2 ] o t h e r w i s e p [ j − 1 ] = ∗ = { d p [ i − 1 ] [ j − 1 ] m a t c h ( s [ i − 1 ] , p [ j − 1 ] ) f a l s e o t h e r w i s e o t h e r w i s e dp[i][j] = \begin{cases} = \begin{cases} dp[i-1][j]\ or\ dp[i][j-2] &match(s[i-1],p[j-2]) \\ dp[i][j-2] &otherwise \end{cases} &p[j-1]=* \\ = \begin{cases} dp[i-1][j-1] & match(s[i-1],p[j-1]) \\ false & otherwise \end{cases} &otherwise \end{cases} dp[i][j]=={dp[i1][j] or dp[i][j2]dp[i][j2]match(s[i1],p[j2])otherwise={dp[i1][j1]falsematch(s[i1],p[j1])otherwisep[j1]=otherwise

S = “ a c b b b " , P = “ a c b b ∗ ” S= “acbbb", P= “acbb*” S=acbbb",P=acbb,我们按照状态转移方程,写出 d p dp dp 中的值:

在这里插入图片描述

算法

class Solution {
public:
	bool isMatch(string s,string p) {
		int m=s.size();
		int n=p.size();
		bool** dp=new bool* [m+1];
		for(int i=0;i<m+1;++i){
			dp[i]=new bool[n+1];
			for(int j=0;j<n+1;++j){
				dp[i][j]=false;
			}
		}
		dp[0][0]=true;
		for(int i=0;i<m+1;++i){
			for(int j=1;j<n+1;++j){
				if(p[j-1]=='*'){
					dp[i][j]=dp[i][j-2];
					if(i!=0&&(p[j-2]=='.'||s[i-1]==p[j-2])){
						dp[i][j]|=dp[i-1][j];
					}
				}
				else{
					if(i!=0&&(p[j-1]=='.'||s[i-1]==p[j-1])){
						dp[i][j]=dp[i-1][j-1];
					}
				}
			}
		}
		return dp[m][n];
	}
};

复杂度分析

假设字符串 s s s 的长度为 m m m,字符串 p p p 的长度为 n n n

  • 时间复杂度: O ( m ⋅ n ) O(m\cdot n) O(mn),因为动态规划中状态的数目为 m ⋅ n m\cdot n mn,在计算每个状态时所需的时间为一个与 m m m n n n 均无关的常量,由动态规划解题的时间复杂度计算公式:
    时 间 复 杂 度 = 状 态 的 数 目 ⋅ 计 算 每 个 状 态 所 需 时 间 时间复杂度=状态的数目⋅计算每个状态所需时间 =所以得到时间复杂度 O ( m ⋅ n ) O(m\cdot n) O(mn)
  • 空间复杂度: O ( m ⋅ n ) O(m\cdot n) O(mn),需要一个 ( m + 1 ) ⋅ ( n + 1 ) (m+1)\cdot (n+1) (m+1)(n+1) 的二维数组 d p dp dp 来存储各个状态的值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值