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
    评论
正则表达式匹配是一个经典的算法问题,主要是判断一个字符串是否能够完全匹配给定的正则表式。 在LeetCode上,也有一道关于正则表达式匹配的题目,题目编号是10。这道题目要求实现一个支持 '.' 和 '*' 的正则表达式匹配,其中 '.' 可以匹配任意单个字符,'*' 可以匹配零个或多个前面的元素。 解决这道题可以使用动态规划的思想,具体的思路如下: 1. 创建一个二维数组dp,dp[i][j]表示s的前i个字符与p的前j个字符是否匹配。 2. 初始化dp为true,表示空字符串与空正则表达式是匹配的。 3. 初始化dp[i]为false,表示非空字符串与空正则表达式是不匹配的。 4. 初始化dp[j],如果p[j-1]是"*",则dp[j]的值取决于dp[j-2]的值,表示将p[j-2]与p[j-1]去掉后的正则表达式是否匹配空字符串。 5. 对于其它的dp[i][j],分成两种情况: - 如果p[j-1]是"."或者与s[i-1]相等,则dp[i][j]的值取决于dp[i-1][j-1]的值,表示将s[i-1]和p[j-1]去掉后的字符串是否匹配。 - 如果p[j-1]是"*",则dp[i][j]的值取决于以下两种情况: - dp[i][j-2]的值,表示将p[j-2]和p[j-1]去掉后的正则表达式是否匹配s的前i个字符。 - dp[i-1][j]的值,表示将s[i-1]与p[j-2]匹配后的字符串是否匹配p的前j个字符。 6. 最后返回dp[s.length()][p.length()]的值,表示整个字符串s与正则表达式p是否完全匹配。 以上是一种使用动态规划解决正则表达式匹配问题的思路,具体的实现可以参考LeetCode官方提供的递归思路的解法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [LeetCode算法 —— 正则表达式匹配(详解官方动态规划思想)](https://blog.csdn.net/weixin_42100963/article/details/106953141)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值