---- from leetcode题库,NO.10 Regular Expression Matching
给定一个字符串 (s
) 和一个字符模式 (p
)。实现支持 '.'
和 '*'
的正则表达式匹配。
‘.’ 匹配任意单个字符。
‘*’ 匹配零个或多个前面的元素。
匹配应该覆盖整个字符串 (s
) ,而不是部分字符串。
说明:
s
可能为空,且只包含从 a-z
的小写字母。p
可能为空,且只包含从 a-z
的小写字母,以及字符 . 和 *。示例 1:
输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。
示例 2:
输入:
s = “aa”
p = “a*”
输出: true
解释: ‘*’ 代表可匹配零个或多个前面的元素, 即可以匹配 ‘a’ 。因此, 重复 ‘a’ 一次, 字符串可变为 “aa”。
示例 3:
输入:
s = “ab”
p = “."
输出: true
解释: ".” 表示可匹配零个或多个(’*’)任意字符(’.’)。
示例 4:
输入:
s = “aab”
p = “cab”
输出: true
解释: ‘c’ 可以不被重复, ‘a’ 可以被重复一次。因此可以匹配字符串 “aab”。
示例 5:
输入:
s = “mississippi”
p = “misisp*.”
输出: false
本题虽然只匹配两种字符,但有这两种字符可组合出多种匹配情况,且由于*
匹配时还需要组合前一个字符才能判断,导致每次匹配时从p中获取的字符数不确定。
为了避免这个问题,在开始匹配前,调用SwitchPattern§,将p中的*
与其之前的字母组合一下,改造为大写字母的形式,将.*
的组合改造成^
的形式,这样就能够实现p中一个字符代表一种匹配方式了。
改造了匹配规则的形式后的匹配方式仍有多种,也就是s中的一个字符可能可以与p中的多个字符匹配,则需要分别对不同的匹配方式分类讨论,对s中的每个字符,每一种匹配方式都应当尝试匹配,若匹配成功一个字符,则可以由递归进入下一个字符的匹配流程,若递归中匹配失败,则需要继续尝试下一种匹配方式,直到匹配成功,直接向上返回True,或四种匹配方式都无法使本次匹配和递归结果匹配成功,返回False。
需要注意的是,每一次尝试匹配时,匹配失败后s和p本身可能已经被递归中的匹配过程改写了,因此在每次尝试新的匹配方式时都应该深拷贝s和p的值作为输入。
尝试了多种思路,最终只有solution7成功了。
重构solution5成功。
def SwitchPattern§:
此函数用于对原字符规律 p做个预处理,将*
和.
翻译成他们所能表示的内容。
由于本题所处理的字符串s只包含小写字母,因此借用与小写字母对应的大写字母表示,即将*
加其前面的字幕替换为了它前面字母的大写字母,p中的大写字母表示匹配0~n个其对应的小写字母。
当*
前的字符是.
时,将*.
替换为^
,用于表示匹配0~n个任意字母。
p中字符.
仍表示匹配任意单个字符。
def isUpWord(w):
参数w为匹配规则p中的单个字符。
此函数用于判断参数w是否可以匹配多个字符,即判断w是否是大写字母或者^
,若w为大写字母或^
则返回True,否则w为小写字母或.
,只能匹配单个字符,返回False。
def pretreatment(sentence,pattern):
此函数用于在每次匹配前判断s或p为空的情况,也是递归的基例,并在s和p都不为空时做每次匹配前的预处理。
分四种情况:
s和p均为空时,说明匹配结束,能匹配上,返回True;
s不空而p为空时,说明p匹配完后s仍有字符剩余无法匹配,匹配失败,返回False;
s为空而p不空时,说明字符串s已匹配完,判断p中是否仍有未匹配完的小写字母,若存在小写字母则匹配失败,返回False,若p中只剩大写字母,则可匹配完,返回true;
s和p均不为空时,说明仍需继续匹配,则为下一步匹配做预处理:取出s的第一个字符和p中可与其匹配的内容,p的开头如果是大写字母,则将连续的大写字母存入队列d中,并取出p中第一个小写字母或者.
,将预处理后的数据全部返回。
def Match1word(sentence,pattern):
此函数用于匹配s中的第一个字符。
在预处理后,已取出了s的第一个待匹配的字符s0,和p中可与之匹配的字符p0和d。
p0是p的第一个非大写字母的字符,包括小写字母或
.
;
d包含p开头的一连串大写字母或^
,从p的第一个字符开始,到遇到第一个小写字母为止.
第一个字符的匹配有四种情况,分别对应match1()、match2()、match3()、match4()如下:
.
;^
匹配。match1()~match4()依次匹配,若有任意一种匹配方式匹配成功,则递归到s中下一个字符的匹配。若四种方式都匹配不上,则说明匹配失败,返回false。
def isMatch(s,p):
此函数为此问题的调用接口。
将字符串s转换成列表的形式,并对原字符规律p做预处理后,进入一个字符一个字符匹配的递归流程。
def SwitchPattern(p):
pattern = list(p)
temp = []
while pattern:
pp = pattern.pop()
if pp == '*':
z = pattern.pop()
if z == '.':
temp.insert(0, '^')
else:
temp.insert(0, z.upper())
else:
temp.insert(0, pp)
return temp
def isUpWord(w):
if not w :
return False
elif (ord(w) >= ord('A')) & ( ord(w ) <= ord( 'Z' )):
return True
elif w=='^':
return True
else:
return False
def pretreatment(sentence,pattern):
#预处理,
#基例
if ( sentence==[] ) & ( pattern==[]):
return True
elif( sentence!=[] ) & ( pattern==[]):
return False
elif( sentence==[] ) & ( pattern!=[]):
while pattern:
if not isUpWord(pattern.pop()):
return False # patter has low word
else: # patter dose not have any low word
return True
elif (sentence != []) & (pattern != []):
# pop all the upwords in pattern to d , and pop the first lowword to p0
d = []
while pattern:
temp = pattern.pop()
if isUpWord(temp):
d.append(temp)
else:
p0 = temp
break
else: # patter dose not have any low word, so give p0 an init value '-'
p0 = '-'
#pop the next match word in sentence to s0
s0 = sentence.pop()
return ( d , s0 , p0 , sentence , pattern )
def match1(s,p):
#s[0]==p[0]
print('match1= sentence={},pattern={}'.format(s, p))
pre = pretreatment( s , p )
if pre ==True:
return True
elif pre == False:
return False
else:
d=pre[0]
s0 = pre [1]
p0 = pre [2]
s_pre = pre[3]
p_pre = pre[4]
if s0 == p0 :
# match the first lowword ,so give up d
sentence = s_pre
pattern = p_pre
return Match1word(sentence,pattern)
else:
return False
def match2(s,p):
#p == '.'
print('match2= sentence={},pattern={}'.format(s, p))
pre = pretreatment( s , p )
if pre ==True:
return True
elif pre == False:
return False
else:
d=pre[0]
s0 = pre [1]
p0 = pre [2]
s_pre = pre[3]
p_pre = pre[4]
if p0 == '.':
# match the first lowword '.' , so give up d
sentence = s_pre
pattern = p_pre
return Match1word(sentence,pattern)
else:
return False
def match3(s,p):
#s[0].upper() in d
print('match3= sentence={},pattern={}'.format(s, p))
pre = pretreatment( s , p )
if pre ==True:
return True
elif pre == False:
return False
else:
d=pre[0]
s0 = pre [1]
p0 = pre [2]
s_pre = pre[3]
p_pre = pre[4]
if s0.upper() in d :
#
sentence = s_pre
pattern = p_pre
if p0 != '-':
pattern.append( p0 )
d = d[d.index(s0.upper()):]
while d :
pattern.append( d.pop() )
return Match1word( sentence , pattern )
else:
return False
def match4(s,p):
#'^' in d
print('match4= sentence={},pattern={}'.format(s, p))
pre = pretreatment(s, p)
if pre == True:
return True
elif pre == False:
return False
else:
d = pre[0]
s0 = pre[1]
p0 = pre[2]
s_pre = pre[3]
p_pre = pre[4]
if '^' in d:
#
sentence = s_pre
pattern = p_pre
if p0 != '-':
pattern.append(p0)
d = d[d.index('^'):]
while d:
pattern.append(d.pop())
return Match1word(sentence, pattern)
else:
return False
#solution7
def Match1word(sentence,pattern):
s = copy.deepcopy(sentence)
p = copy.deepcopy(pattern)
if match1(s,p):
return True
s = copy.deepcopy(sentence)
p = copy.deepcopy(pattern)
if match2(s,p):
return True
s = copy.deepcopy(sentence)
p = copy.deepcopy(pattern)
if match3(s,p):
return True
s = copy.deepcopy(sentence)
p = copy.deepcopy(pattern)
if match4(s,p):
return True
else:
return False
其他的solution1~尝试失败的思路,要么是无法遍历全部的匹配方式,要么是运行超时。
下面是失败的尝试。
前向遍历:根据p从前往后开始匹配
问题在于,没有递归,一种匹配方式失败之后无法退回去尝试另一种匹配方式。
##solution1
def ForwardMatch1( s , p ):
P=p.split('*')
s=list(s)
d=[]
print(P,s,d)
for j in P[:-1]:
print('sub_p={},s={}'.format(j, s,d))
for i in j[:-1]:
if i=='.':
d.append(s.pop(0))
elif i!=s[0]:
return False
else:
d.append(s.pop(0))
saved=j[-1]
print('saved={},s={}'.format(saved, s ,d ))
if s:
while (s[0]==saved)|(saved=='.'):
d.append(s.pop(0))
if len(s)==0:
break
print('saved={},s={}\n'.format(saved, s , s))
print('\nsub_p={},s={}'.format(P[-1], s ,s))
for i in P[-1]:
if s=='':
return False
if i == '.':
d.append(s.pop(0))
elif s:
if i != s[0] :
return False
else:
d.append(s.pop(0))
else:
return False
print('s={}'.format(s, s))
if len(s)!=0:
return False
else:
return True
后向遍历:由p从结尾开始一个一个弹出字符匹配。好处是处理问题时,遇到多弹出一个就行,
问题同solution1一样,没有递归,一种匹配方式失败之后无法退回去尝试另一种匹配方式。
##solution2
def BackwardMatch(s, p):
pattern = list( p )
txst = list( s )
d = []
if (pattern==[]) :
return True if txst == [] else False
else:
pp = pattern.pop()
if (txst==[]) :
if (pp != '*' ):
return False
else:
while pp == '*':
d.append(pattern.pop())
if pattern == []:
return True
pp = pattern.pop()
else:
return False
while (txst):
i=txst.pop()
if pattern == [] :
if i in d:
d = d[d.index(i):]
elif '.' in d:
d = d[d.index('.'):]
while pp == '*':
d.append( pattern.pop() )
if pattern==[]:
pp=[]
break
pp =pattern.pop()
#print('i={},\td={},\tpp={},\ttxst={},\tpattern={}'.format(i, d, pp, txst, pattern))
if i == pp:
if i in d:
d = d[ d.index( i ) : ]
else:
d = []
pp= [] if pattern == [] else pattern.pop()
elif i in d:
ind = d.index( i )
d = d[ ind : ]
elif pp == '.':
d = []
pp = [] if pattern == [] else pattern.pop()
elif '.' in d:
ind = d.index('.')
d = d[ind:]
else:
return False
else:
while pp == '*':
d.append( pattern.pop() )
if pattern==[]:
pp=[]
break
pp =pattern.pop()
if (pp ==[] ) & (pattern == []):
return True
return False
另一种前向遍历,solution1的问题仍在。
##solution3
def ForwardMatch(s, p):
txst = list(s)
pattern = SwitchPattern(p)
print('pattern = {},\ttxst = {}'.format(pattern,txst))
d = []
if ( pattern==[] ) & ( txst==[]):
return True
elif( pattern==[] ) & ( txst!=[]):
return False
else:
pp = pattern.pop(0)
while txst:
w = txst.pop(0)
while isUpWord(pp ):
d.append(pp.lower())
pp = [] if pattern == [] else pattern.pop(0)
print('w={}\tpp={}\td={}\ttxst={}\tpattern={}'.format(w, pp, d, txst, pattern))
if w == pp:
if w in d :
d = d[d.index(w):]
else:
d=[]
pp = [] if pattern==[] else pattern.pop(0)
elif '^' in d:
d = d[d.index('^'):]
elif w in d:
d = d[d.index(w):]
elif pp == '.':
pp = [] if pattern == [] else pattern.pop(0)
else:
return False
else: #txst==[] pp有值 pp = pattern.pop(0)
#print('w={}\tpp={}\td={}\ttxst={}\tpattern={}'.format(w, pp, d, txst, pattern))
if ( pp !=[] ) & ( not isUpWord(pp)) :
return False
while pattern:
if not isUpWord(pattern.pop(0)):
return False
else :
return True
前向遍历和后向遍历的结合,先尝试后向遍历,匹配失败时再前向遍历尝试一下。
问题在于仍无法解决多匹配造成的混乱问题,可能存在某些匹配前向和后向匹配都不通,但从中间向两边就能匹配上。问题根源仍是无法遍历到所有的匹配可能性。
##solution4
def isMatch4( s , p ):
print('s={},\tp={}'.format( s , p ) )
return True if BackwardMatch( s , p ) else ForwardMatch( s , p )
第一次尝试递归的方式解决此问题,逻辑上应该是行得通的,但由于代码分块不清晰,条件判断太多,某些条件参数指定可能有bug,而出bug时递归层次太深,无法通过debug准确定位出问题的位置,导致代码无法修正,只能重构。
##solution5
def Match1word5(sentence,pattern): #backword match
print( 'START-->{: <60}\t{}'.format(str(sentence),str(pattern )))
i=0
if ( pattern==[] ) & ( sentence==[]):
print('return True ---pattern , sentence all empty!')
return True
elif( pattern==[] ) & ( sentence!=[]):
print('return False --- pattern empty , but sentence not empty')
return False
else: #pattern not empty
d = []
while pattern:
pp = pattern.pop()
if isUpWord( pp ):
d.append( pp )
else:
p = pp
break
else: #patter dose not have any low word
p = []
if not sentence : #sentence empty
if ( p !=[] ) :
print('False--sentence empty, and p != []')
return False
while pattern:
if not isUpWord(pattern.pop()):
print('False--sentence empty, and pattern has low word')
return False
else :
print('return True--sentence empty,and pattern all up word')
return True
else: #sentence is not empty
word = sentence.pop()
print('\t word={},\tp={},\td={}'.format(word, p, d))
#print('sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern, word, p, d))
d_saved= copy.deepcopy(d)
pattern_saved = copy.deepcopy(pattern)
sentence_saved = copy.deepcopy(sentence)
if ( word == p) :
#print('SAVED ==[p=word]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern, word,p, d))
if Match1word( sentence, pattern ):
print('word == p:return True')
return True
else:
d = d_saved
pattern = pattern_saved
sentence = sentence_saved
if p == '.':
#print('SAVED ==[p="."]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern, word,p, d))
if Match1word(sentence, pattern):
print('p == . :return True')
return True
else:
d = d_saved
pattern = pattern_saved
sentence = sentence_saved
#print('SAVED ==[p="."]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern, word, p, d))
if word.upper() in d :
#print('SAVED ==[word.upper() in d]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence,pattern, word, p, d))
d = d [ d.index( word.upper()) : ]
if p:
pattern.append(p)
while d :
pattern.append( d. pop() )
if Match1word(sentence, pattern) :
print('word.upper() in d :return True')
return True
else:
d = d_saved
pattern = pattern_saved
sentence = sentence_saved
#print('SAVED ==[word.upper() in d]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence,pattern,word, p, d))
if '^' in d:
#print('1SAVED ==["^" in d ]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern,word, p, d))
d = d[d.index('^'):]
if p:
pattern.append(p)
while d:
pattern.append(d.pop())
#print('2SAVED ==["^" in d ]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern,word, p, d))
if Match1word(sentence, pattern):
print('^ in d :return True')
return True
else:
#print('3SAVED ==["^" in d ]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern,word, p, d))
return False
else:
print('else false')
return False
第一次尝试重构solution5,由于代码耦合性太高,未明确分块,导致重构失败。
#solution6
def Match1word6(sentence,pattern): #backword match
print( 'START-->{: <60}\t{}'.format(str(sentence),str(pattern )))
if ( pattern==[] ) & ( sentence==[]):
#print('return True ---pattern , sentence all empty!')
return True
elif( pattern==[] ) & ( sentence!=[]):
#print('return False --- pattern empty , but sentence not empty')
return False
else: #pattern not empty
d = []
while pattern:
pp = pattern.pop(0)
if isUpWord( pp ):
d.append( pp )
else:
p = pp
break
else: #patter dose not have any low word
p = []
if not sentence : #sentence empty
if ( p !=[] ) :
#print('False--sentence empty, and p != []')
return False
else :
#print('return True--sentence empty,and pattern all up word')
return True
else: #sentence is not empty
word = sentence.pop(0)
#print('\t word={},\tp={},\td={}'.format(word, p, d))
#print('sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern, word, p, d))
d_saved= copy.deepcopy(d)
pattern_saved = copy.deepcopy(pattern)
sentence_saved = copy.deepcopy(sentence)
if ( word == p) :
print('1SAVED ==[p=word]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern, word,p, d))
if Match1word( sentence, pattern ):
#print('word == p:return True')
return True
else:
d = d_saved
pattern = pattern_saved
sentence = sentence_saved
if p == '.':
print('2SAVED ==[p="."]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern, word,p, d))
if Match1word(sentence, pattern):
#print('p == . :return True')
return True
else:
d = d_saved
pattern = pattern_saved
sentence = sentence_saved
#print('SAVED ==[p="."]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern, word, p, d))
if word.upper() in d :
print('3SAVED ==[word.upper() in d]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence,pattern, word, p, d))
d = d [ d.index( word.upper()) : ]
if p:
pattern.insert(0,p)
while d :
pattern.insert( 0,d. pop(0) )
if Match1word(sentence, pattern) :
#print('word.upper() in d :return True')
return True
else:
d = d_saved
pattern = pattern_saved
sentence = sentence_saved
#print('SAVED ==[word.upper() in d]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence,pattern,word, p, d))
if '^' in d:
print('4SAVED ==["^" in d ]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern,word, p, d))
d = d[d.index('^'):]
if p:
pattern.insert(0,p)
while d:
pattern.insert(0,d.pop(0))
#print('2SAVED ==["^" in d ]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern,word, p, d))
if Match1word(sentence, pattern):
#print('^ in d :return True')
return True
else:
#print('3SAVED ==["^" in d ]== sentence={},\t pattern={},\t word={},\tp={},\td={}'.format(sentence, pattern,word, p, d))
return False
else:
#print('else false')
return False
1.复杂问题一定要先理清思路,借助递归来简化问题;
2.递归的方式一定要明确,统一递归的入口,考虑好递归结果失败后如何进行下一步;
3.代码分块,降低代码耦合度,也有助于理清思路;
4.深拷贝来保护递归传参的数据。