LeetCode-Regular Expression Match 解析与备忘

LeetCode-Regular Expression Match 解析与备忘

问题描述

题目链接:https://leetcode.com/problems/regular-expression-matching/description/
给定义一个字符串s与匹配模式p,返回一个bool值:
- 若sp完全匹配,返回True
- 反之,返回False

其中,s中只包含a-z,而p中包含a-z,以及.*.可以代表任一字符,*表示前面的字符出现零次、一次或多次。

Example
s = "ab"
p = ".*"
返回True,因为p可以看作为..,匹配ab

Example
s = "mississippi"
p = "mis*is*p*."
返回False

  • 网上此类博客甚多,此处主要作为自己的备忘,分析肯定不及大佬们精细,偶有错误,无伤大雅。

递归

简单直接的想法,就是依次对比每个字符:
1. 如果p在第i个位置的字符p[i]!=*,判断p[i]==s[i],或者p[i] == '.',相等就跳到i+1的位置,继续对比p[i+1]s[i+1],否则返回False,不匹配;
2. 如果p[i]=='*',检查前一个位置p[i-1]s[i]是否相等,
2a. 若p[i-1]!=s[i],表明*只能表示0个元素,继续匹配p[i+1]s[i]
2b. 若p[i-1]==s[i],则*可能代表至少一个元素,也有可能代表0个元素,任何一种情况为真时,都可以返回True。(由于多个元素可以分解为单个元素,所以只需要递归检查*代表一个元素和代表0个元素两种情况)

实现如下:

bool isMatch(string s, string p)
{
    /* p.size() == 0 */
    if(p.empty()) return s.empty(); 

    /* p.size() == 1 */
    if(p.size() == 1) return s.size() == 1 && (s[0] == p[0] || p[0] == '.'); 

    /* p.size() >= 2 */
    if (p[1] != '*')
        return !s.empty() && (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p.substr(1));
    else
    {
        if (s.empty() || (s[0] != p[0] && p[0] != '.')) 
            return isMatch(s, p.substr(2));
        else
            return isMatch(s.substr(1), p) || isMatch(s, p.substr(2));
    }
}

主要问题就是理清逻辑,算无遗策。另有博客从字符串末尾开始匹配,其判断过程大同小异,不赘述。

动态规划

我不甚了解动态规划的思想,有大佬推荐入门博客,但是我还没看–.

简要说下这里用动态规划的问题,我的初步理解是减少一些不必要的计算开销。很多时候有些过程都是重复计算的,但是因为在递归里,不停地跳入跳出,没有保存中间值,所以没办法利用好这些已经计算的值,而进行许多的重复计算。

我们考虑一张bool类型的表,其值表示匹配与否,一个带图的例子可以参照这里

s = "bbbba"p = ".*a*a"为例,我们首先构建一张表T,1表示匹配,0表示不匹配。通常,我们都会多一行一列(此处为空字符串)用于初始化的方便:

其中,为了保持spT的下标看起来具有一致性,我们将初始的行数和列数记为-1。但第-1行和-1列在初始化时已经赋予,实际中ij从0开始取。T[-1][-1]为1,因为空字符串与空字符串相等,其余为零;

基于之前递归的规则,我们考虑我们的状态转移:
- 当p[i]!='*'时,当且仅当p[i]==s[i] || p[i]=='.'时,T[i][j]有机会为1。当之前的子字符串都匹配时,此时当前字符也匹配,才会有T[i][j]=1。注意到我们在初始状态多了一行一列,但是在建立表T时,默认初始行(列)为-1,因此T[i][j]实际存储的是p[i]s[j]的状态信息。在此之前的子字符串匹配状态存储在T[i-1][j-1],由此T[i][j]=(p[i]==s[i] || p[i]=='.') && T[i-1][j-1](类比在递归的此情况下,我们会将s的下一位和p的下一位进行匹配:s[j+1]p[i+1]),其余情况为0;
- 当p[i]=='*'时,和之前的递归一样,我们考虑两种情况:*代表0次,以及*代表1次(多次也可以由1次叠加)。当p[i-1]!=s[j] && p[i-1]!='.',此时*只能代表0次,而此时的状态应该由此前pi-2时刻决定(因为i-1时刻与i时刻将要被踢除),即T[i][j]=T[i-2][j](类比在递归的此情况下,我们会将p的下两位字符与s比较:s[j]p[i+2]);当p[i-1]=s[j] || p[i-1]!='.'时,*可能代表1次(多次),也可能代表0次,两种情况任一为真皆可。当*代表0次时,如前,T[i][j]=T[i-2][j];当*代表一次时,T[i][j]=T[i][j-1](类比在递归的此情况下,我们会将s的下一位与p进行匹配:s[j+1]p[i])。

根据以上分析,给出接下来T的取值变化。
首先是初始化第-1列:

为了简便,一下以短横线代替箭头,表示取值的转移。

实现如下:

bool isMatch(string s, string p)
{
    if (p.empty()) return s.empty();
    if (p.size() == 1)return s.size() == 1 && (s[0] == p[0] || p[0] == '.');

    int sdims = (int)s.size(), pdims = (int)p.size();
    int i, j;

    /* construct the table, and initialize */
    char **maps = (char **)malloc((pdims+1) * sizeof(char *));
    for (i = 0; i <= pdims; ++i)
        maps[i] = (char *)malloc((sdims + 1) * sizeof(char));

    maps[0][0] = 1;
    for (i = 1; i <= pdims; ++i)
        maps[i][0] = p[i - 1] == '*' && maps[i-2][0];
    for (j = 1; j <= sdims; ++j)
        maps[0][j] = 0;

    /* run */
    for (i = 1; i <= pdims; ++i)
        for (j = 1; j <= sdims; ++j)
        {
            if (p[i - 1] != '*')
                maps[i][j] = (p[i - 1] == s[j - 1] || p[i - 1] == '.') && maps[i - 1][j - 1];
            else
                maps[i][j] = maps[i - 2][j] || ((p[i - 2] == s[j - 1] || p[i - 2] == '.') && maps[i][j - 1]);
        }

    bool ret = maps[pdims][sdims] != 0;

    /* deallocation */
    for (i = 0; i <= pdims; ++i)
        free(maps[i]);
    free(maps);

    return ret;
}

以上是本渣琢磨了一天的成果……编程太菜慢慢刷题中……勿喷么么哒


我的Github:https://github.com/Sissuire
第一手博客来源:https://sissuire.github.io/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值