[LeetCode]10.正则表达式匹配

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 = "c*a*b"
输出: true
解释: 因为 ‘*’ 表示零个或多个,这里 ‘c’ 为 0 个, ‘a’ 被重复一次。因此可以匹配字符串 “aab”。

示例 5:

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

思路

​ 如果p字符串中没有正则表达式则是简单的字符串匹配,但是加入了两条正则规则,第一条规则为p中含有'.',这条规则还是很好匹配,因为’.'可以匹配任何字符,关键是第二条规则'*''*'表示前面一个字符出现0次或者多次,所以我们需要将'*'和它前一个字符当做一个整体考虑

​ 如果采用暴力法,每次在p中遇到某个字符+'*',将它们作为整体依次尝试'*'之前的字符出现0次,1次,2次…,的情况去匹配s串,这样过于复杂。动态规划可以解决很多字符串的问题,如最长公共子序列等,尝试使用动态规划求解。

​ 定义状态dp[i][j] 表示:s的前i个字符与p的前j个字符是否匹配,dp[i][j]为1表示存在一种匹配方案,为0表示不匹配。

状态计算

  • 对于普通字符(小写英文字符)如果 s [ i ] = = p [ j ] s[i] == p[j] s[i]==p[j],并且 d p [ i − 1 ] [ j − 1 ] = = 1 dp[i-1][j-1] == 1 dp[i1][j1]==1,则 d p [ i ] [ j ] = 1 dp[i][j] = 1 dp[i][j]=1

  • 如果 p [ j ] = = ′ . ′ p[j] == '.' p[j]==.,并且 d p [ i − 1 ] [ j − 1 ] = = 1 dp[i-1][j-1] == 1 dp[i1][j1]==1,则 d p [ i ] [ j ] = 1 dp[i][j] = 1 dp[i][j]=1

综合以上两种情况可以将代码写成如下

 if (i > 0 && p[j] != '*')
 	dp[i][j] = (s[i] == p[j] || dp[i-1][j-1]) && dp[i - 1][j - 1];
   //或者:dp[i][j] = match(s, i, p, j) && dp[i - 1][j - 1]; match见后面说明
  • 如果 p [ j ] = = ′ ∗ ′ p[j] == '*' p[j]==,按照暴力算法的思路,我们需要枚举’*'和其前面的字符出现0次,1次,2次…的情况去匹配s串,所以

    • '*'和它前面的字符出现0次时: d p [ i ] [ j ] = d p [ i ] [ j − 2 ] dp[i][j] = dp[i][j-2] dp[i][j]=dp[i][j2]

    • '*'和它前面的字符出现1次时: d p [ i ] [ j ] = ( s [ i ] = = p [ j − 1 ] ∣ ∣ p [ j − 1 ] = = ′ . ′ ) & & d p [ i − 1 ] [ j − 2 ] dp[i][j] = (s[i] == p[j-1] || p[j-1] == '.') \&\& dp[i-1][j-2] dp[i][j]=(s[i]==p[j1]p[j1]==.)&&dp[i1][j2]

    • '*'和它前面的字符出现2次时: d p [ i ] [ j ] = ( s [ i ] = = p [ j − 1 ] ∣ ∣ p [ j − 1 ] = = ′ . ′ ) & & ( s [ i − 1 ] = = p [ j − 1 ] ∣ ∣ p [ j − 1 ] = = ′ . ′ ) & & d p [ i − 2 ] [ j − 2 ] dp[i][j] = (s[i] == p[j-1] || p[j-1] == '.') \&\& (s[i-1] == p[j-1] || p[j-1] == '.')\&\&dp[i-2][j-2] dp[i][j]=(s[i]==p[j1]p[j1]==.)&&(s[i1]==p[j1]p[j1]==.)&&dp[i2][j2]

    • 为了简化表达设: m a t c h ( s [ i ] , p [ j − 1 ] ) = ( s [ i ] = = p [ j − 1 ] ∣ ∣ p [ j − 1 ] = = ′ . ′ ) match(s[i], p[j-1])=(s[i] == p[j-1] || p[j-1] == '.') match(s[i],p[j1])=(s[i]==p[j1]p[j1]==.),其含义为s[i]p[j]相等或者p[j-1]'.',表示s[i]p[j-1]匹配。

    • 当然'*'和它前面的字符还有出现3次,4次…等各种状态,但是现在已经根据上面的推导可以总结规律了,记一下公式为公式1

    d p [ i ] [ j ] = d p [ i ] [ j − 2 ]    ∣ ∣    d p [ i − 1 ] [ j − 2 ] & & m a t c h ( s [ i ] , p [ j − 1 ] )    ∣ ∣    d p [ i − 2 ] [ j − 2 ] & & m a t c h ( s [ i ] , p [ j − 1 ] ) & & m a t c h ( s [ i − 1 ] , p [ j − 1 ] ) dp[i][j] = dp[i][j-2] \ \ || \ \ dp[i-1][j-2] \&\& match(s[i],p[j-1])\ \ || \ \ dp[i-2][j-2] \&\& match(s[i],p[j-1]) \&\& match(s[i-1],p[j-1]) dp[i][j]=dp[i][j2]    dp[i1][j2]&&match(s[i],p[j1])    dp[i2][j2]&&match(s[i],p[j1])&&match(s[i1],p[j1])

  • 将上面公式中i替换成i-1公式2
    d p [ i − 1 ] [ j ] = d p [ i − 1 ] [ j − 2 ]    ∣ ∣    d p [ i − 2 ] [ j − 2 ] & & m a t c h ( s [ i − 1 ] , p [ j − 1 ] )    ∣ ∣    d p [ i − 3 ] [ j − 2 ] & & m a t c h ( s [ i − 1 ] , p [ j − 1 ] ) & & m a t c h ( s [ i − 2 ] , p [ j − 1 ] ) dp[i-1][j] = dp[i-1][j-2] \ \ || \ \ dp[i-2][j-2] \&\& match(s[i-1],p[j-1])\ \ || \ \ dp[i-3][j-2] \&\& match(s[i-1],p[j-1]) \&\& match(s[i-2],p[j-1]) dp[i1][j]=dp[i1][j2]    dp[i2][j2]&&match(s[i1],p[j1])    dp[i3][j2]&&match(s[i1],p[j1])&&match(s[i2],p[j1])
    可以发现将公式2左右两边同时 & & m a t c h ( s [ i ] , p [ j − 1 ] ) \&\&match(s[i],p[j-1]) &&match(s[i],p[j1]),则等于公式1第2项起后面的部分

  • 所以最终的公式为 d p [ i ] [ j ] = d p [ i ] [ j − 2 ]    ∣ ∣    d p [ i − 1 ] [ j ]    & &    m a t c h ( s [ i ] , p [ j − 1 ] ) dp[i][j] = dp[i][j-2]\ \ ||\ \ dp[i-1][j]\ \ \&\& \ \ match(s[i],p[j-1]) dp[i][j]=dp[i][j2]    dp[i1][j]  &&  match(s[i],p[j1])

代码如下:

if (p[j] == '*')
	dp[i][j] = dp[i][j-2] || f[i-1][j] && match(s[i], p[j-1]);//注意判断下标大于0

​ 最后起始状态为 d p [ 0 ] [ 0 ] = 1 dp[0][0] = 1 dp[0][0]=1表示两个空串可以匹配。代码如下,细节见注释

class Solution {
public:
    bool match(string& s, int i, string& p, int j) {
        if (s[i] == p[j]) return true;
        if (p[j] == '.') return true;
        return false;
    }
    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        s = ' ' + s;//前面加上空字符串,因为如果p开头就出现:某个字符+'*'则可以匹配空字符
        p = ' ' + p;
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1));
        dp[0][0] = 1;//空字符串可以匹配

        for (int i = 0; i <= m; i++) {
            for (int j = 1; j <= n; j++) {//j从1开始,因为任何字符串都不可能和空字符串匹配
                if (j + 1 <= n && p[j + 1] == '*') continue;//*必须和它前一个字母一起使用
                if (i > 0 && p[j] != '*') {//普通字符或者p[j] == '.'
                    dp[i][j] = match(s, i, p, j) && dp[i - 1][j - 1];//s[i] == p[j] && dp[i-1][j-1]
                } else if (p[j] == '*') {
                    dp[i][j] = j-2 >= 0 && dp[i][j-2] || i-1 >= 0 && dp[i-1][j] && match(s, i, p, j - 1);
                }
            }
        }
        return dp[m][n];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值