【leetcode】正则表达式匹配

文章目录

      • TOPIC
      • 分析
      • 解决方案
        • solution7----pass
          • 分析
          • 代码
        • solution1
          • 分析
          • 代码
        • solution2
          • 分析
          • 代码
        • solution3
          • 分析
          • 代码
        • solution4
          • 分析
          • 代码
        • solution5
          • 分析
          • 代码
        • solution6
          • 分析
          • 代码
      • 总结


TOPIC

---- 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成功了。

solution7----pass

分析

重构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()如下:

  • s0与p0匹配,p0是小写字母;
  • s0与p0匹配,p0是.
  • s0与d中的一个大写字母匹配;
  • s0与d中的^匹配。

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~尝试失败的思路,要么是无法遍历全部的匹配方式,要么是运行超时。
下面是失败的尝试。

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


solution2

分析

后向遍历:由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


solution3

分析

另一种前向遍历,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

分析

前向遍历和后向遍历的结合,先尝试后向遍历,匹配失败时再前向遍历尝试一下。
问题在于仍无法解决多匹配造成的混乱问题,可能存在某些匹配前向和后向匹配都不通,但从中间向两边就能匹配上。问题根源仍是无法遍历到所有的匹配可能性。

代码
##solution4
def isMatch4( s , p ):
    print('s={},\tp={}'.format( s , p ) )
    return True if BackwardMatch( s , p ) else ForwardMatch( s , p )


solution5

分析

第一次尝试递归的方式解决此问题,逻辑上应该是行得通的,但由于代码分块不清晰,条件判断太多,某些条件参数指定可能有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


solution6

分析

第一次尝试重构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.深拷贝来保护递归传参的数据。


你可能感兴趣的:(python,算法)