LeetCode-10. 正则表达式匹配

31 篇文章 0 订阅
3 篇文章 0 订阅

地址:https://leetcode-cn.com/problems/regular-expression-matching/

思路:

一.DP: 大佬太强了orz,思路清晰

状态
首先状态 dp 一定能自己想出来。
dp[i][j] 表示 s 的前 i 个是否能被 p 的前 j 个匹配

转移方程
怎么想转移方程?首先想的时候从已经求出了 dp[i-1][j-1] 入手,再加上已知 s[i]、p[j],
要想的问题就是怎么去求 dp[i][j]。

已知 dp[i-1][j-1] 意思就是前面子串都匹配上了,不知道新的一位的情况。
那就分情况考虑,所以对于新的一位 p[j] s[i] 的值不同,要分情况讨论:

1.考虑最简单的 p[j] == s[i] : dp[i][j] = dp[i-1][j-1]
然后从 p[j] 可能的情况来考虑,让 p[j]=各种能等于的东西。

2.p[j] == "." : dp[i][j] = dp[i-1][j-1]

3.p[j] == "*" :
第一个难想出来的点:怎么区分 *? 的两种讨论情况
首先给了 *,明白 * 的含义是 匹配零个或多个前面的那一个元素,所以要考虑他前面的元素 p[j-1]。
* 跟着他前一个字符走,前一个能匹配上 s[i],* 才能有用,前一个都不能匹配上 s[i],* 也无能为力,
只能让前一个字符消失,也就是匹配 0 次前一个字符。
所以按照 p[j-1] 和 s[i] 是否相等,我们分为两种情况:

3.1 p[j-1] != s[i] : dp[i][j] = dp[i][j-2]
这就是刚才说的那种前一个字符匹配不上的情况。
比如(ab, abc* )。遇到 * 往前看两个,发现前面 s[i] 的 ab 对 p[j-2] 的 ab 能匹配,
虽然后面是 c*,但是可以看做匹配 0 次 c,相当于直接去掉 c*,所以也是 True。注意 (ab, abc*) 是 False。

3.2 p[j-1] == s[i] or p[j-1] == ".":
* 前面那个字符,能匹配 s[i],或者 * 前面那个字符是万能的 .
因为 .* 就相当于 ..,那就只要看前面可不可以匹配就行。
比如 (##b , ###b*),或者 ( ##b , ###.* ) 只看 ### 后面一定是能够匹配上的。
所以要看 b 和 b* 前面那部分 ## 的地方匹不匹配。
第二个难想出来的点:怎么判断前面是否匹配
dp[i][j] = dp[i-1][j] // 多个字符匹配的情况	
or dp[i][j] = dp[i][j-1] // 单个字符匹配的情况
or dp[i][j] = dp[i][j-2] // 没有匹配的情况	
看 ### 匹不匹配,不是直接只看 ### 匹不匹配,要综合后面的 b b* 来分析
这三种情况是 oror 的关系,满足任意一种都可以匹配上,同时是最难以理解的地方:

dp[i-1][j] 就是看 s 里 b 多不多, ### 和 ###b* 是否匹配,一旦匹配,s 后面再添个 b 也不影响,
因为有 * 在,也就是 ###b 和 ###b*也会匹配。

dp[i][j-1] 就是去掉 * 的那部分,###b 和 ###b 是否匹配,比如 qqb qqb

dp[i][j-2] 就是 去掉多余的 b*,p 本身之前的能否匹配,###b 和 ### 是否匹配,
比如 qqb qqbb* 之前的 qqb qqb 就可以匹配,那多了的 b* 也无所谓,因为 b* 可以是匹配 0 次 b,相当于 b * 可以直接去掉了。

三种满足一种就能匹配上。

为什么没有 dp[i-1][j-2] 的情况?就是 ### 和 ### 是否匹配?因为这种情况已经是 dp[i][j-1] 的子问题。也就是 s[i]==p[j-1],则 dp[i-1][j-2]=dp[i][j-1]。

最后来个归纳:
如果 p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1];
如果 p.charAt(j) == '.' : dp[i][j] = dp[i-1][j-1];
如果 p.charAt(j) == '*':
如果 p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] //in this case, a* only counts as empty
如果 p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == '.':
dp[i][j] = dp[i-1][j] //in this case, a* counts as multiple a
or dp[i][j] = dp[i][j-1] // in this case, a* counts as single a
or dp[i][j] = dp[i][j-2] // in this case, a* counts as empty

作者:kao-la-7
链接:https://leetcode-cn.com/problems/regular-expression-matching/solution/dong-tai-gui-hua-zen-yao-cong-0kai-shi-si-kao-da-b/
来源:力扣(LeetCode)

需要初始化第一行该题解才能成立 自下而上实现 大佬的归纳中的第三种情况的第二个情况的第二个条件其实是多余的
or dp[i][j] = dp[i][j-1] // in this case, a* counts as single a
原因在于检查多个字符匹配的时候也是要落到检查单个字符最后落到检查empty,所以我们只需要实现检查多个字符和empty字符的情况。
作者:zavier-lyu
来源:力扣(LeetCode)

二.DFS:对于 '*' 同样分0次匹配和多次匹配进行搜索

Code 1:

#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
#include<unordered_map>
#include<cstring>
using namespace std;
typedef long long LL;

// dp[i][j]表示s[0,i-1]和p[0,j-1]范围内是匹配的
class Solution {
public:
    bool isMatch(string s, string p) {
        int n=s.length();
		int m=p.length();
		bool dp[n+5][m+5];
		memset(dp,0,sizeof(dp));
		dp[0][0]=true;
		for(int i=0;i<=n;++i)
			for(int j=1;j<=m;++j)
				if(p[j-1]=='*'){
					dp[i][j]=dp[i][j-2]||(i>=1&&(s[i-1]==p[j-2]||p[j-2]=='.')&&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[n][m];
    }
};

int main()
{
	string s1,s2;
	Solution So;
	cin>>s1>>s2;
	cout<<So.isMatch(s1,s2)<<endl;
	
	return 0;
}

Code 2:

//DFS 
class Solution {
private:
	int n,m;
	string s,p;
	vector<vector<int>> d;
public:
    bool isMatch(string s, string p) {
    	this->s=s;
    	this->p=p;
        n=s.length();
		m=p.length();
		d=vector<vector<int>>(n+1,vector<int>(m+1,-1));
		return DFS(0,0);
    }
    
    bool DFS(int k1,int k2){
    	if(k1>n||k2>m)	return false;
		if(k2==m)	return (k1==n);
		if(d[k1][k2]!=-1)	return d[k1][k2];
		bool boo=s[k1]==p[k2]||p[k2]=='.';
		if(k2+1<m&&p[k2+1]=='*'){
			d[k1][k2]=DFS(k1,k2+2)||(boo&&DFS(k1+1,k2));
		}else{
			d[k1][k2]=boo&&DFS(k1+1,k2+1);
		}
		return d[k1][k2];
	}
};

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值