初次看到这个题目的时候,因为刚上完一个计算理论的课。然后第一反应哎,这题我熟啊!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