LeetCode P10. Regular Expression Matching 正则表达式匹配的一种递归解法,以及由此延伸出的DP思路

初次看到这个题目的时候,因为刚上完一个计算理论的课。然后第一反应哎,这题我熟啊!DFA自动机啊!然后狂喜,而且就两个通配字符。结果时间超时了...究其原因应该是在某些特定的匹配模式p下,自动机的迭代次会呈现指数增长。比如a*a*a*a*a*a*a*a*a*a*a*a*a*a*b 这种,所以思路不对(或者是没有学到位,自动机的迭代出错了)

然后就开始考虑其他的方法。

之后开始考虑分治法,从后面最后一个p开始匹配,然后确定结果后递归剩余的前面的部分。因为题目规则种已经说明每个*之前必有一个非*的输入。

那么就存在两种情况,首先是最后一个是字母,那么只需要看p[-1](p的最后一个字母,python记法,下同)与s[-1]是否相等,如果否,则返回False。

如果是相等的,那么就递归判断isMatch(s[:-1], p[:-1])

而如果当前的p[-1]是*,只要p[-2]跟从s后面开始的字符相等,那么我们可以匹配任意长度的p[-2]

知道遇到不相等的。比如说:s = adcddd;  p = a*bc*d*, 那么d* 可以匹配0,1,2,3个d。

isMatch(s, p[-2]) 这个表示,最后的这个* 什么也不匹配

isMatch(s[:-1], p[-2]) 这表示匹配一个p[-2]

等等等等...

而最终的结果是,只要这里面有一个真,那么这个递归的返回值就是真。

 

递归已经建立起来了,然后结束条件就是 p的长度为0的时候,如果s为0那么返回真,否则返回假

然后还有几个特殊的边界条件,比如说遇到s长度为0,p不为0的时候。根据p最后一位是不是* 来判断 结果。

再就是 . 等于任意字符

递归的版本:

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
            # terminate condition
            if len(p) == 0:
                return len(s) == 0
            res = False

            if p[-1] == '*':
                res = res or self.isMatch(s, p[:-2])     # * can match 0 times. Thus first situation is match 0 times

                i = -1
                while i >= -len(s) and (s[i] == p[-2] or p[-2] == '.'):
                    res = res or self.isMatch(s[:i], p[:-2])
                    i -= 1
                return res
            else:
                if len(s) == 0:
                    return False
                
                if (s[-1] == p[-1] or p[-1] == '.'):
                    return self.isMatch(s[:-1], p[:-1])
                else:
                    return False

 

然后很明显,这个递归里很多的子状态被求解了多次。那么我们可以对它进行一下剪枝。

利用一个二维数组,DP[m][n] 来表示 s[0:m] 与p[0:n] 是不是匹配。这个是不是也叫记忆化搜索?我也不确定。 这样之后速度从原来的快于35%的人,变成了65%的人。

同样,这个其实就是一种反向的DP。因为在我的理解中,基本上所有的普通DP的原理或者说思路就是来自于递归的剪枝。正向的DP只需要把这个递归翻译成while版本就好了。(当然...我没有这么做) 状态转移方程就是这里的递归的转移条件。而base case 就是 这几个特例。

加入剪枝的代码:

dp = [[-1 for i in range(31)] for j in range(21)]
class Solution:
    def isMatch(self, s, p):
        global dp
        dp = [[-1 for i in range(31)] for j in range(21)]
        return self.newIsMatch(s,p)
    
    def newIsMatch(self, s: str, p: str) -> bool:
                global dp
                if not dp[len(s)][len(p)] == -1:
                    if dp[len(s)][len(p)] == 1:
                        return True
                    else:
                        return False

                # terminate condition
                if len(p) == 0:
                    if len(s) == 0:
                        dp[len(s)][len(p)] = 1
                        return True
                    else:
                        dp[len(s)][len(p)] = 0
                        return False

                res = False

                if p[-1] == '*':
                    res = res or self.newIsMatch(s, p[:-2])  # * can match 0 times. Thus first situation is match 0 times

                    i = -1
                    while i >= -len(s) and (s[i] == p[-2] or p[-2] == '.'):
                        res = res or self.newIsMatch(s[:i], p[:-2])
                        i -= 1
                    if res:
                        dp[len(s)][len(p)] = 1
                        return True
                    else:
                        dp[len(s)][len(p)] = 0
                        return False

                else:
                    if len(s) == 0:
                        dp[len(s)][len(p)] = 0
                        return False

                    if (s[-1] == p[-1] or p[-1] == '.'):
                        if self.newIsMatch(s[:-1], p[:-1]):
                            dp[len(s)][len(p)] = 1
                            return True
                        else:
                            dp[len(s)][len(p)] = 0
                            return False
                    else:
                        dp[len(s)][len(p)] = 0
                        return False

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值