1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        ?LeetCode刷題實(shí)戰(zhàn)10:字符串正則匹配

        共 4255字,需瀏覽 9分鐘

         ·

        2020-08-17 19:56

        算法的重要性,我就不多說(shuō)了吧,想去大廠,就必須要經(jīng)過(guò)基礎(chǔ)知識(shí)和業(yè)務(wù)邏輯面試+算法面試。所以,為了提高大家的算法能力,這個(gè)公眾號(hào)后續(xù)每天帶大家做一道算法題,題目就從LeetCode上面選 !


        今天和大家聊的問(wèn)題叫做正則表達(dá)式匹配,我們先來(lái)看題面:


        Given an input string (s) and a pattern (p), 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).


        https://leetcode.com/problems/regular-expression-matching/


        Note:

        • s?could be empty and contains only lowercase letters?a-z.
        • p?could be empty and contains only lowercase letters?a-z, and characters like?.?or?*.


        題意


        這道題屬于典型的人狠話不多的問(wèn)題,讓我們動(dòng)手實(shí)現(xiàn)一個(gè)簡(jiǎn)單的正則匹配算法。不過(guò)為了降低難度,這里需要匹配的只有兩個(gè)特殊符號(hào),一個(gè)符號(hào)是'.',表示可以匹配任意的單個(gè)字符。還有一個(gè)特殊符號(hào)是'*',它表示它前面的符號(hào)可以是任意個(gè),可以是0個(gè)。


        題目要求是輸入一個(gè)母串和一個(gè)模式串,請(qǐng)問(wèn)是否能夠達(dá)成匹配。


        示例 1:

        輸入:
        s = "aa"
        p = "a"
        輸出: false
        解釋: "a" 無(wú)法匹配 "aa" 整個(gè)字符串。
        示例 2:

        輸入:
        s = "aa"
        p = "a*"
        輸出: true
        解釋: 因?yàn)?'*' 代表可以匹配零個(gè)或多個(gè)前面的那一個(gè)元素, 在這里前面的元素就是 'a'。因此,字符串 "aa" 可被視為 'a' 重復(fù)了一次。
        示例 3:

        輸入:
        s = "ab"
        p = ".*"
        輸出: true
        解釋: ".*" 表示可匹配零個(gè)或多個(gè)('*')任意字符('.')。
        示例 4:

        輸入:
        s = "aab"
        p = "c*a*b"
        輸出: true
        解釋: 因?yàn)?'*' 表示零個(gè)或多個(gè),這里 'c' 為 0 個(gè), 'a' 被重復(fù)一次。因此可以匹配字符串 "aab"。
        示例 5:

        輸入:
        s = "mississippi"
        p = "mis*is*p*."
        輸出: false

        題解


        這題要求的是完全匹配,而不是包含匹配。也就是說(shuō)s串匹配完p串之后不能有剩余,比如剛好完全匹配才行。明確了這點(diǎn)之后,我們先來(lái)簡(jiǎn)化操作,假設(shè)不存在'*'這個(gè)特殊字符,只存在'.',那么顯然,這個(gè)簡(jiǎn)化過(guò)后的問(wèn)題非常簡(jiǎn)單,我們隨便就可以寫出代碼:

        def match(s, p):  n = len(s)  for i in range(n):    if s[i] == p[i] or p[i] == '.':      continue    return False  return True


        我們下面考慮加入'*'的情況,其實(shí)加入'*'只會(huì)有一個(gè)問(wèn)題,就是'*'可以匹配任意長(zhǎng)度,如果當(dāng)前位置出現(xiàn)了'*',我們并不知道它應(yīng)該匹配到哪里為止。我們不知道需要匹配到哪里為止,那么就需要進(jìn)行搜索了。也就是說(shuō),我們需要將它轉(zhuǎn)換成一個(gè)搜索問(wèn)題來(lái)進(jìn)行求解。我們?cè)囍眠f歸來(lái)寫一下:

        def match(s, p, i, j):    # 當(dāng)前位置是否匹配    flag = s[i] == p[j] or p[j] == '.'    # 判斷p[j+1]是否是*,如果是那么說(shuō)明p[j]可以跳過(guò)匹配    if j+1 < len(p) and p[j+1] == '*':        # 兩種情況,一種是跳過(guò)p[j],另一種是p[j]繼續(xù)匹配        return match(s, p, i, j+2) or (flag and match(s, p, i+1, j))    else:        # 如果沒(méi)有*,只有一種可能        return flag and match(s, p, i+1, j+1)


        這段代碼的精髓在于,由于'*'之前的符號(hào)也可以是0個(gè),所以我們不能判斷當(dāng)前位置是否會(huì)是'*',而要判斷后面一個(gè)位置是否是'*'。這句話看起來(lái)有些像是繞口令,但是卻是這道題的精髓,如果看不懂的話,可以結(jié)合一下代碼思考。

        總之,就是以出否出現(xiàn)'*'為基點(diǎn),分情況進(jìn)行遞歸即可。

        從代碼上來(lái)看算上注釋才12行,可是將這里面的關(guān)系都梳理清楚,并不容易。還是非??简?yàn)基本功的,需要對(duì)遞歸有較深入的理解才行。不過(guò),這并不是最好的方法,因?yàn)槟銜?huì)發(fā)現(xiàn)有很多狀態(tài)被重復(fù)計(jì)算了很多次。這也是遞歸算法經(jīng)常遇到的問(wèn)題之一,要解決倒也不難,我們很容易發(fā)現(xiàn),對(duì)于固定的i和j,答案是固定的。那么,我們可以用一個(gè)數(shù)組來(lái)存儲(chǔ)所有的i和j的情況。如果當(dāng)前的i和j處理過(guò)了,那么直接返回結(jié)果,否則再去計(jì)算。

        這種方法稱作記憶化搜索,說(shuō)起來(lái)復(fù)雜,但是實(shí)現(xiàn)起來(lái)只需要加幾行代碼:

        memory = {}def match(s, p, i, j):    if (i, j) in memory:      return memory[(i, j)]    # 當(dāng)前位置是否匹配    flag = s[i] == p[j] or p[j] == '.'    # 判斷p[j+1]是否是*,如果是那么說(shuō)明p[j]可以跳過(guò)匹配    if j+1 < len(p) and p[j+1] == '*':        # 兩種情況,一種是跳過(guò)p[j],另一種是p[j]繼續(xù)匹配        ret = match(s, p, i, j+2) or (flag and match(s, p, i+1, j))    else:        # 如果沒(méi)有*,只有一種可能        ret = flag and match(s, p, i+1, j+1)    memory[(i, j)] = ret    return ret


        如果你對(duì)動(dòng)態(tài)規(guī)劃足夠熟悉的話,想必也應(yīng)該知道,記憶化搜索本質(zhì)也是動(dòng)態(tài)規(guī)劃的一種實(shí)現(xiàn)方式。但同樣,我們也可以選擇其他的方式實(shí)現(xiàn)動(dòng)態(tài)規(guī)劃,就可以擺脫遞歸了,相比于遞歸,使用數(shù)組存儲(chǔ)狀態(tài)的遞推形式更容易理解。


        我們用dp[i][j]存儲(chǔ)s[:i]與p[:j]是否匹配,那么根據(jù)我們之前的結(jié)論,如果p[j-1]是'*',那么dp[i][j]可能由dp[i][j-2]或者是dp[i-1][j]轉(zhuǎn)移得到。dp[i][j-2]比較容易想到,就是'*'前面的字符作廢,為什么是dp[i-1][j]呢?這種情況是代表'*'連續(xù)匹配,因?yàn)榭赡芷ヅ淙我鈧€(gè),所以必須要匹配在'*'這個(gè)位置。


        舉個(gè)例子:

        s = 'aaaaa'
        p = '.*'


        在上面這個(gè)例子里,'.'能匹配所有字符,但是問(wèn)題是s中只有一個(gè)a能匹配上。如果我們不用dp[i-1][j]而用dp[i-1][j-1]的話,那么是無(wú)法匹配aa或者aaa這種情況的。因?yàn)檫@幾種情況都是通過(guò)'*'的多匹配能力實(shí)現(xiàn)的。如果還不理解的同學(xué), 建議仔細(xì)梳理一下它們之間的關(guān)系。


        我們用數(shù)組的形式寫出代碼:


        def is_match(s, p):    # 為了防止超界,我們從下標(biāo)1開(kāi)始    s = '$' + s    p = '$' + p    n, m = len(s), len(p)    dp = [[False for _ in range(m)] for _ in range(n)]
        dp[0][0] = True
        # 需要考慮s空串匹配的情況 for i in range(n): for j in range(1, m): # 標(biāo)記當(dāng)前位置是否匹配,主要考慮s為空串的情況 match = True if i > 0 and (s[i] == p[j] or p[j] == '.') else False # 判斷j位置是否為'*' if j > 1 and p[j] == '*': # 如果是,只有兩種轉(zhuǎn)移的情況,第一種表示略過(guò)前一個(gè)字符,第二種表示重復(fù)匹配 dp[i][j] = dp[i][j-2] or ((s[i] == p[j-1] or p[j-1] == '.') and dp[i-1][j]) else: # 如果不是,只有一種轉(zhuǎn)移的可能 dp[i][j] = dp[i-1][j-1] and match return dp[n-1][m-1]

        這題的代碼并不長(zhǎng),算上注釋也不過(guò)20行左右。但是當(dāng)中對(duì)于題意的思考,以及對(duì)算法的運(yùn)用很高,想要AC難度還是不低的。希望大家能夠多多揣摩,理解其中的精髓,尤其是最后的動(dòng)態(tài)規(guī)劃解法,非常經(jīng)典,推薦一定要弄懂。當(dāng)然如果實(shí)在看不明白也沒(méi)有關(guān)系,在以后的文章,我會(huì)給大家詳細(xì)講解動(dòng)態(tài)規(guī)劃算法。

        今天的文章就到這里,如果覺(jué)得有所收獲,請(qǐng)順手點(diǎn)個(gè)在看或者轉(zhuǎn)發(fā)吧,你們的支持是我最大的動(dòng)力。



        上期推文:


        LeetCode刷題實(shí)戰(zhàn)1:在數(shù)組上遍歷出花樣

        LeetCode刷題實(shí)戰(zhàn)2:用鏈表模擬加法

        LeetCode刷題實(shí)戰(zhàn)3:最長(zhǎng)不重復(fù)子串

        LeetCode刷題實(shí)戰(zhàn)4:兩個(gè)正序數(shù)組的中位數(shù)

        LeetCode刷題實(shí)戰(zhàn)5:判斷回文子串

        LeetCode刷題實(shí)戰(zhàn)6:Z字形變換

        LeetCode刷題實(shí)戰(zhàn)7:整數(shù)反轉(zhuǎn)

        LeetCode刷題實(shí)戰(zhàn)8:字符串轉(zhuǎn)換整數(shù)

        LeetCode刷題實(shí)戰(zhàn)9:求解回文數(shù)


        瀏覽 20
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            天天色图 | 欧美一级特黄A片免费观看密森 | 操比操| 宝贝腿开大一点你真湿h | 操比视频免费观看 | 无码波多野结衣 | 国产在线欧美豆花 | 日皮视频黄片 | 欧美成人网站在线观看 | 国产成人小视频 |