LeetCode题目解析(二):10、Regular Expression Matching

10、正则表达式匹配

一、问题描述:
实现一个支持’.’和’*’的正则表达式匹配算法。其中’.’可以匹配任意字符,’*’可以使其前面的字符匹配一个或多个。匹配应当覆盖整个字符串而不是部分。
给出的函数原型为bool isMatch(const char *s, const char *p)
例如:
isMatch(“aa”,”a”) → false
isMatch(“aa”,”aa”) → true
isMatch(“aaa”,”aa”) → false
isMatch(“aa”, “a*”) → true
isMatch(“aa”, “.*”) → true
isMatch(“ab”, “.*”) → true
isMatch(“aab”, “c*a*b”) → true
isMatch(“aaa”, “aaaa*”)→ true

二、问题分析
可以发现,此题的难度来自引入的’.’和’*’字符。其中’.’字符的匹配很好解决,只要重新定义两字符相等的条件就可以,如对于s中第i个位置的字符和p中第j个位置的字符,判断相等的条件写作:s[i] == p[j] || p[j] == ‘.’ 。这样就解决了’.’字符问题。
但是’*’就困难多了,我们称附带’*’的字符为可变字符,每个可变字符的长度都是不确定的,假设有n个可变字符,字符串s和p的长度分别为slen和plen,设每个可变字符的长度分别为L1、L2……Ln,满足L1 + L2 + …… + Ln = slen - plen,本质则可能性为n个字符,在长度slen - plen上的整数部分切分。根据动态规划中锯木头问题的分析,这样做的时间复杂度为指数级别。
这里需要引入动态规划的求解思路。首先,为了简化问题,我们对p字符串进行预处理,剔除’*’并用数组v[]记录字符的可变性,如p[i]是可变的我们就把v[i]置位1,不可变则为0,预处理后的字符串记为q,长度为qlen。为了进一步分析问题,我们可以将s中从0到i的字串,与p中0到j的字串,两者是否匹配记为r[i][j],r是一个布尔型二维数组。因此最后我们需要的答案就是r[slen][qlen]。既然如此,我们可以讨论一般情况,即r[i][j]满足的性质。下面分四种情况讨论:
1、若q[j]不是可变字符,且s[i]与q[j]不相等(这里的不相等是重新定义过的,即包含了q[j]不为’.’这层含义),则已经可以判定两者不匹配,直接赋值为false。
2、若q[j]不是可变字符,且s[i]与q[j]相等,则两者匹配,答案就是s[i-1][j-1]。
3、若q[j]是可变字符,且s[i]与q[j]不相等,那么可变字符的数目必然是0,应对跳过q串中该字符的匹配,即赋值为s[i][j-1]。
4、最后一种情况相对复杂,若q[j]是可变字符,且s[i]与q[j]相等,有两种可能性,一种是可变字符长度为0,即q中跳过该字符,如这种情况,isMatch(“aaa”, “aaaa*”),就应该跳过最后一个a的匹配,也可以是s[i]确实是与q[j]匹配,那么两种情况下有一种成立,答案即为真。可见这种情况应该赋值为s[i-1][j] || s[i][j-1]。

所以任务就变成了按规则填表。
归纳了一般情况,我们来填写初始状态。首先考虑s[0][0],该位置表示s和q都为空,显然是匹配的,答案是true。再考虑s[i][0] (1 <= i <= slen),表示q串为空,s不为空,两者不可能匹配,答案为false。再考虑s[0][j] (1 <= j <= qlen),表示s串为空,q串不为空,这里就有点讲究了,若q串中全为可变字符,那么它们的数量只需都为0,已然可以匹配,但若其中有一个不可变字符,就无法匹配,因此可以用s[0][j-1] || v[j]来递推出结果。

下面我们有s = “aabcdef” p = “a*b*cda*ef*f” 的表格填写完来举例,可以目测,两者是匹配的。图中,红色表示可变字符。
初始化状态:
初始化状态
第一行填写:
第一行
第二、三行填写:
二、三行
剩余部分:
剩余
可以清晰地看到,s与p是匹配的。

三、Ac code

class Solution {
public:
    bool isMatch(string s, string p) {
        if (!p.length() && s.length())    return false;
        if (!p.length() && !s.length())   return true;
        string q;
        int v[p.length()] = {0};
        //预处理
        for (int i = 0; i < p.length(); i++) {
            if (p[i] != '*')    q += p[i];
            else v[q.length() - 1] = 1;
        }
        //初始条件
        int slen = s.length(), qlen = q.length();
        int r[slen+1][qlen+1];
        r[0][0] = 1;
        for (int i = 1; i <= slen; i++)   r[i][0] = 0;
        for (int j = 1; j <= qlen; j++)   r[0][j] = r[0][j-1] && v[j-1];
        //递推
        for (int i = 0; i < slen; i++) {
            for (int j = 0; j < qlen; j++) {
                if (!v[j] && (s[i] == q[j] || q[j] == '.'))      r[i+1][j+1] = r[i][j];
                else if (v[j] && (s[i] != q[j] && q[j] != '.'))  r[i+1][j+1] = r[i+1][j];
                else if (!v[j] && (s[i] != q[j] && q[j] != '.')) r[i+1][j+1] = 0;
                else if (v[j] && (s[i] == q[j] || q[j] == '.'))  r[i+1][j+1] = r[i][j+1] || r[i+1][j];;
            }
        }

        return r[slen][qlen];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值