LeetCode | 10. Regular Expression Matching *

题目

Implement regular expression matching with support for '.' and '*'.

'.' Matches any single character.
'*' Matches zero or more of the preceding element.

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *p)

Some examples:
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

思路

此题第一次做没想出来怎么解,最后参考了这篇文章。详细的思路请见该博客。

看完该博客的思路和代码之后,发现很多情况都可以用到递归的,递归也可以使代码更加优雅。

代码

bool helper(const char *s, const char *p, int i, int j) {
    int sLen = strlen(s);
    int pLen = strlen(p);

    if(j == pLen)
        return i == sLen;

    if((j == pLen - 1) || (p[j + 1] != '*')) {
        if((i == sLen) || ((s[i] != p[j]) && (p[j] != '.')))
            return false;
        else {
            return helper(s, p, i + 1, j + 1);
        }
    }

    // p[j + 1] == '*'
    while((i < sLen) && ((p[j] == '.') || (s[i] == p[j]))) {
        if(helper(s, p, i, j + 2))
            return true;
        i++;
    }

    return helper(s, p, i, j + 2);
}

bool isMatch(const char *s, const char *p) {
    return helper(s, p, 0, 0);
}

下图是上述代码运行的一个例子:

另一种思路:动态规划

这篇文章所述,上述所用的递归方法会存在重复计算的问题,导致效率不高。下面用动态规划的思想解决此题(动态规划的思想可以参考《算法导论》第15章或者这篇文章)。

还是以上面的两个字符串(s = "abcdddg"p = "a.*d*g")为例。其中,字符串 s 的长度记为 sLen,字符串 p 的长度记为 pLen

先构造一个 (pLen + 1)(sLen + 1) 列的二维数组(表) table[][] 用于记录当前的结果。这个二维数组的元素取值为 true 或者 false

其中,table[j][i]true 表示字符串 p 的前 j 个字符组成的字符串(p[0 ... j - 1])能够匹配 s 的前 i 个字符组成的字符串(s[0 ... i - 1])。依此类推,当 table[pLen][sLen]true 时,则 sp 相匹配,即 isMatch() 函数返回 true

table[][] 初始状态和匹配完成后的状态:

Note: 索引 ij 在字符串与二维数组之间的关系。p[j]s[i] 两个字符对应在 table[][] 中的位置是 table[j + 1][i + 1]p[j], s[i] => table[j + 1][i + 1])。

在对比时主要分如下几种情况。

p[j] != '*':如果字符 p[j] == s[i] 或者 p[j] == '.'(即 p[j]s[i] 这两个字符匹配),那么这两个字符在 table[j + 1][i + 1] 这个点的结果与字符 p[j - 1]s[i - 1]table[j][i] 这个点的结果一样,即 table[j + 1][i + 1] = table[j][i]

p[j] != '*' 这种情况的判断代码为:

for(i = 0; i < sLen; i++) {
    if(s[i] == p[j] || p[j] == '.') {
        table[j + 1][i + 1] = table[j][i];
    }
}

p[j] == '*' && p[j - 1] == '.':即字符串 p 中出现 ".*" 这个子字符串。当然,这种情况需要 j > 0

".*" 这个可以匹配任何字符串。如题目所述,'.' 匹配任意单个字符('.' 可以替换成 'a''b'……);'*' 可以替换成0个、1个或者多个前一个字符('a*' 可以替换成 '''a''aa''aaa'……)。

因此,当子字符串 s[0 ... i]".*" 前面的子字符串(即 p[0 ... j - 2])匹配(这个条件相当于 table[j - 1][i + 1]true),那么 s 的子字符串 s[0 ... i + 1]s[0 ... i + 2]、…、s[0 ... sLen - 1] 都与 p 的子字符串 p[0 ... j] 相匹配(这个结果就相当于 table[j + 1][i + 1]table[j + 1][i + 2]、…、table[j + 1][sLen] 均为 true)。

还有一些特殊的情况需要用到 table[j][i + 1] 去判断:

s = "aa", p = ".*",这样的话上述的子字符串 p[0 ... j - 2] 就不存在了(此时 j = 1j - 2 不存在)。因此此时需要判断 table[j][i + 1]true 还是 false 了。

s = "ba", p = "a*.*",这种情况也需要判断 table[j][i + 1]

p[j] == '*' && p[j - 1] == '.' 这种情况的判断代码为:

i = 0;
while(i < sLen && !table[j - 1][i + 1] 
    && !table[j][i + 1]) {
    i++;
}

for(; i < sLen; i++) {
    table[j + 1][i + 1] = true;
}

p[j] == '*' && p[j - 1] != '.':这种情况下,table[j - 1][i + 1] 或者 table[j][i + 1] 这两个条件上面已经讲过。i > 0 && j > 0 && table[j + 1][i] && s[i] == s[i - 1] && s[i - 1] == p[j - 1] 这个条件表示子字符串 s[0 ... i - 1] 与子字符串 p[0 ... j] 匹配并且字符 s[i]s[i - 1]p[j - 1] 匹配。

p[j] == '*' && p[j - 1] != '.' 这种情况的判断代码如下:

for(i = 0; i < sLen; i++) {
    if(table[j - 1][i + 1] || table[j][i + 1]
        || i > 0 && j > 0 && table[j + 1][i]
        && s[i] == s[i - 1] && s[i - 1] == p[j - 1]) {
        table[j + 1][i + 1] = true;
    }
}

代码如下。这版代码提交到 leetcode.com 后,可以看到所用的时间的确比递归的方法短。

代码

bool isMatch(const char *s, const char *p) {
    int i = 0, j = 0;
    int sLen = strlen(s);
    int pLen = strlen(p);
    bool res = false;
    bool **table = NULL;

    if(sLen == 0 && pLen == 0)
        return true;

    if(pLen == 0)
        return false;

    // Initialize table with false.
    table = (bool **)malloc((pLen + 1) * sizeof(bool*));
    for(j = 0; j <= pLen; j++) {
        *(table + j) = (bool *)malloc((sLen + 1) * sizeof(bool));
    }
    for(j = 0; j <= pLen; j++) {
        for(i = 0; i <= sLen; i++) {
            table[j][i] = false;
        }
    }

    table[0][0] = true;
    i = 0;
    j = 0;

    for(j = 0; j < pLen; j++) {
        if(p[j] != '*') {
            for(i = 0; i < sLen; i++) {
                if(s[i] == p[j] || p[j] == '.') {
                    table[j + 1][i + 1] = table[j][i];
                }
            }
        }
        else {
            if(j > 0) {
                if(table[j - 1][0])
                    table[j + 1][0] = true;
                if(p[j - 1] == '.') {
                    i = 0;
                    while(i < sLen && !table[j - 1][i + 1] 
                        && !table[j][i + 1]) {
                        i++;
                    }

                    for(; i < sLen; i++) {
                        table[j + 1][i + 1] = true;
                    }
                }
                else {
                    for(i = 0; i < sLen; i++) {
                        if(table[j - 1][i + 1] || table[j][i + 1]
                            || i > 0 && j > 0 && table[j + 1][i]
                            && s[i] == s[i - 1] && s[i - 1] == p[j - 1]) {
                            table[j + 1][i + 1] = true;
                        }
                    }
                }
            }
        }
    }

    res = table[pLen][sLen];

    for(j = 0; j <= pLen; j++) {
        free(*(table + j));
        *(table + j) = NULL;
    }
    free(table);
    table = NULL;

    return res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值