LeetCode 10.正则表达式匹配


正则表达式匹配


思路1:递归回溯

难点: * 出现的次数

  • 把所有的情况都尝试一遍
总体思路:通过不断的剪去s和p相同的首部,直到某一个或两个都被剪空,就可以得到结论。
大致可以分为两种情况:包含*,不包含*
  • 不包含* (最简单):只需要依次比较
if(s[i] == p[i] || p[i] == '.') {
	return isMatch(s.substr(1), p.substr(1));
}
  • 包含*
    • p的第i个元素在s中出现0次:保持 s 不变,将 p 剪去两个元素,继续调用isMatch。
    • p的第i个元素在s中出现一次或者多次:若 p 和 s 首元素相同,则保持 p 不变,将 s 剪去一个元素
代码如下:
class Solution {
public:
	bool isMatch(string s, string p) {
		if(p.empty()) return s.empty();
		
		bool first_match = !s.empty() && (s[0] == p[0] || p[0] == '.');
		
		if(p.size() >= 2 && p[1] == '*') {
			return (isMatch(s, p.substr(2)) || first_match && isMatch(s.substr(1), p));
		} else {
			return first_match && isMatch(s.substr(1), p.substr(1));
		}
	}
}
  • 方法1因每种匹配方式都会尝试,导致重复多余的比较
    • 理论时间复杂度:~= O( 2 m a x ( m , n ) 2^{max(m,n)} 2max(m,n))
    • 空间复杂度:~= O( 2 m a x ( m , n ) 2^{max(m,n)} 2max(m,n))

在这里插入图片描述
不知为啥效率很低,希望后期能优化

思路2:动态规划

dp[i][j] 表示 s 的前 i 个字符与 p 中的前 j 个字符是否匹配
写法一:

时间复杂度:O(mn),空间复杂度:O(mn)

class Solution {
public:
	bool first_match(string s, string p, int i, int j) {
		return s[i] == p[j] || p[j] == '.';
	}

	bool isMatch(string s, string p) {
	
		int m = s.size(), n = p.size();
		
		bool dp[m+1][n+1];
		memset(dp, false, sizeof dp);
		
		for(int i = 2; i <= n; ++i) dp[0][i] = p[i-1] == '*' && dp[0][i-2];

		for(int i = 0; i < m; ++i) {
			for(int j = 0; j < n; ++j) {
				if(p[j] == '*') {
					//
					dp[i+1][j+1] = dp[i+1][j-1] || first_match(s, p, i, j-1) && dp[i][j+1];
				} else {
					dp[i+1][j+1] = first_match(s, p, i, j) && dp[i][j];
				}
			}
		}
		return dp[m][n];
	}
}

同样的问题,效率极低?
在这里插入图片描述

  • 效率低的原因:有很多无效判断!!!
写法二:
分析:

难点: * 表示前一个字符出现零次或者多次,复杂的地方就在于 * 的不唯一性。

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 s s s 的前 i i i 个字符与 p p p 中的前 j j j 个字符是否能够匹配
考虑 p p p 的第 j j j 个字符的匹配情况:
  1. 如果 p p p 的第 j j j 个字符是一个小写字母:那么,必须在 s s s 中匹配一个相同的字母,即:
    d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] , s [ i ] = p [ j ] f a l s e , s [ i ] ≠ p [ j ] dp[i][j] = \left\{\begin{matrix} dp[i-1][j-1], \qquad s[i]=p[j]\\false,\qquad \qquad \qquad s[i]\ne p[j]\end{matrix}\right. dp[i][j]={dp[i1][j1],s[i]=p[j]false,s[i]=p[j]
就是说此种情况下:
如果 s[i] != p[j] ,那么dp[i][j] = false;
如果 s[i] == p[j],那么dp[i][j] 取决于 dp[i-1][j-1] 的匹配情况
  1. 如果 p p p 的第 j j j 个字符是 *:那么就表示我们可以对 p p p 的第 j − 1 j−1 j1 个字符匹配任意自然数次。
  • 匹配 0 次:此时, p p p 中 ‘字符 + *’ 没有匹配任何 s s s 中的字符。
    d p [ i ] [ j ] = d p [ i ] [ j − 2 ] dp[i][j] = dp[i][j-2] dp[i][j]=dp[i][j2]
  • 匹配 1、2、3…次:
    d p [ i ] [ j ] = d p [ i − 1 ] [ j − 2 ] , i f s [ i ] = p [ j − 1 ] dp[i][j] = dp[i-1][j-2],\qquad if \quad s[i]=p[j-1] dp[i][j]=dp[i1][j2],ifs[i]=p[j1]
    d p [ i ] [ j ] = d p [ i − 2 ] [ j − 2 ] , i f s [ i − 1 ] = s [ i ] = p [ j − 1 ] dp[i][j] = dp[i-2][j-2],\qquad if \quad s[i-1]=s[i]=p[j-1] dp[i][j]=dp[i2][j2],ifs[i1]=s[i]=p[j1]
    d p [ i ] [ j ] = d p [ i − 3 ] [ j − 2 ] , i f s [ i − 2 ] = s [ i − 1 ] = s [ i ] = p [ j − 1 ] dp[i][j] = dp[i-3][j-2],\qquad if \quad s[i-2]=s[i-1]=s[i]=p[j-1] dp[i][j]=dp[i3][j2],ifs[i2]=s[i1]=s[i]=p[j1]
    . . . . . . . . . . . . ............ ............

此时,状态转移方程如下:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] o r d p [ i ] [ j − 2 ] , s [ i ] = p [ j − 1 ] d p [ i ] [ j − 2 ] , s [ i ] ≠ p [ j − 1 ] dp[i][j] = \left\{\begin{matrix} dp[i-1][j]\quad or\quad dp[i][j-2], \qquad s[i]=p[j-1]\\ dp[i][j-2], \quad \quad \qquad \qquad \qquad \qquad s[i]\ne p[j-1]\end{matrix}\right. dp[i][j]={dp[i1][j]ordp[i][j2],s[i]=p[j1]dp[i][j2],s[i]=p[j1]
解释:
\qquad s [ i ] ≠ p [ j − 1 ] s[i] \ne p[j-1] s[i]=p[j1],此时,只能匹配 0 次, d p [ i ] [ j ] dp[i][j] dp[i][j] 取决于 d p [ i ] [ j − 2 ] dp[i][j-2] dp[i][j2]
\qquad s [ i ] = p [ j − 1 ] s[i] = p[j-1] s[i]=p[j1],此时,
\qquad \qquad 情况1:匹配 s s s 末尾的一个字符,将该字符扔掉,而该组合还可以继续进行匹配;
\qquad \qquad 情况2:不匹配字符,将该组合扔掉,不再进行匹配。

  1. 如果 p p p 的第 j j j 个字符是 .:那么 p [ j ] p[j] p[j] 一定成功匹配 s s s 中的任意一个小写字母
最终的状态转移方程如下:

d p = f dp = f dp=f
在这里插入图片描述

代码如下:
class Solution {
public:
	bool isMatch(string s, string p) {
	
		int m = s.size(), n = p.size();
		
		// 为了判断s[i]和p[j]是否能够匹配(lambda匿名函数写法)
		auto matches = [&]() {
			// s、p 的范围都是[1, 20]
			/* 
				注意下标,全局的 i,j 表示dp数组中的行列
				所以dp[i][j]其实表示 s[i-1] 与 p[j-1] 是否能够匹配。
			*/
			if(i == 0) return false; // s[0][j] === false;  
			if(p[j-1] == '.') return true; // 当p[j-1] = '.', 匹配任意字符
			return s[i-1] == p[j-1]; // s[i] == p[j]
		};
		
		bool dp[m+1][n+1];
		memset(dp, false, sizeof dp);
		dp[0][0] = true; // s = ‘’,p = ‘’
		
		for (int i = 0; i <= m; ++i) {
			for (int j = 1; j <= n; ++j) {
				// --> dp[i][j]
				// 两种情况:1. p 当前字符(p[j-1])是‘*’;2. 不是‘*’
				if (p[j-1] == '*') {
					/*
						为啥是 dp[i][j] |= ....,为啥是与等于?(注意状态转移方程)
						可能是为了提高效率?
						如果是先写 dp[i][j] = dp[i][j-2];则要 |=
						否则,不需要
					*/
					dp[i][j] |= dp[i][j-2]; // 此时 * 表示匹配 0 次
					if (matches(i, j-1)) dp[i][j] |= dp[i-1][j];
				} else {
					if (matches(i, j)) dp[i][j] = dp[i-1][j-1];
				}
			}
		}
		
		return dp[m][n];
	}
}

😘😘😘😘😘😘😘😘😘😘😘😘😘😘😘😘😘😘

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值