記一次算法優化

這段時間刷了刷letcode,編程的樂趣可能就是`它就在那兒,而你要征服它`(哈哈哈),刷過一道題時,會有種莫名其妙的快感!

本篇文章記錄了我刷的一道算法題並經歷不斷優化和改進且最終"攀頂"的歷程。

題目優先:letcode-44_通配符匹配

題解:我的題解

'?' 可以匹配任何單個字符。
'*' 可以匹配任意字符串(包括空字符串)。

示例:(抄自 letcode)


輸入:
s = "aa"
p = "a"
輸出: false
解釋: "a" 無法匹配 "aa" 整個字符串。


輸入:
s = "aa"
p = "*"
輸出: true
解釋: '*' 可以匹配任意字符串。


輸入:
s = "cb"
p = "?a"
輸出: false
解釋: '?' 可以匹配 'c', 但第二個 'a' 無法匹配 'b'。


輸入:
s = "adceb"
p = "*a*b"
輸出: true
解釋: 第一個 '*' 可以匹配空字符串, 第二個 '*' 可以匹配字符串 "dce".


輸入:
s = "acdcb"
p = "a*c?b"
輸入: false

*匹配的字符串具有不確定性,但是我們只要找出一種成功pattern匹配str的可能即可,並不一定需要遍歷所有可能(剪枝);

?相比*只佔一個字符的空間(確定的),所以任何一個字符(這裏的字符指[a-zA-Z0-9_])都可以匹配?;而任意字符串都能匹配*(包括空字符串),(瞭解正則的同學應該知道貪婪匹配和懶惰匹配,先列出這兩個名詞,稍後在算法中會有體現) ;

如果pattern中包含*, 在不確定*應該匹配哪一子串的情況下,需要遍歷每一種可能的情況,直到找到解,需要回溯;如果pattern不包含*,則只需要比較pattern和str上對應位置的字符,而?可以和任一字符匹配,不需要回溯。

pattern中包含*的匹配流程圖例:(以懶惰匹配舉例,展示核心算法)

 

然而通過構造特殊的pattern和str,該流程的計算時間將會指數級增加(有關正則表達式注入請了解一下);所以需要對pattern進行優化並剪去不可能的分支。

先展示一下我的提交列表(前前後後共進行了5次優化

v0.1 版本

func sMatch(pattern, str []rune) bool {
    if len(str) <= 0 {
        if len(pattern) <= 0 {
            return true
        }
        for _, c := range pattern {
            if c != '*' {
                return false
            }
        }
        return true
    }
    if len(pattern) <= 0 {
        return false
    }
    sl := len(str)
    pIndex := 0
    i := 0
    for i = 0; i < sl; i++ {
        if pIndex >= len(pattern) {
            break
        }
        switch pattern[pIndex] {
        case '*':
            gl := sl
            flag := false
            for gl >= i && gl >= 0{
                if sMatch(pattern[pIndex+1:], str[gl:]) {
                    flag = true
                    break
                }
                gl-- //丟棄一個
            }
            return flag
        case '?':
            pIndex++
        default:
            if str[i] != pattern[pIndex] {
                return false
            }
            pIndex++
        }
    }
    if i == len(str) && pIndex != len(pattern) {
        for i := pIndex; i < len(pattern); i++ {
            if pattern[i] != '*' {
                return false
            }
        }
        return true
    }
    if pIndex != len(pattern) || i != len(str) {
        return false
    }
    return true
}

func isMatch(s string, p string) bool {
    if len(s) <= 0 {
        if len(p) <= 0 {
            return true
        }
        for _, c := range p {
            if c != '*' {
                return false
            }
        }
        return true
    }
    if len(p) <= 0 {
        return false
    }
    return sMatch([]rune(p), []rune(s))
}

死在了這個case上:

str: "bbbababbbbabbbbababbaaabbaababbbaabbbaaaabbbaaaabb"
pattern: "*b********bb*b*bbbbb*ba"

v0.1 以貪婪匹配規則回溯的,然後將算法改爲以懶惰匹配規則回溯,成功通過這個case 

v0.2版本

```省略
        case '*':
            gl := i
            flag := false
            for gl < sl{
                if pIndex == len(pattern)-1 {
                    return true
                }
                if sMatch(pattern[pIndex+1:], str[gl:]) {
                    flag = true
                    break
                }
                gl++ //要一個
            }
            return flag
```省略

 然而又死在了這個case上:

str: "aaaabaaaabbbbaabbbaabbaababbabbaaaababaaabbbbbbaabbbabababbaaabaabaaaaaabbaabbbbaababbababaabbbaababbbba"
pattern: "*****b*aba***babaa*bbaba***a*aaba*b*aa**a*b**ba***a*a*"

 突然,我想到相連的多個*不就等於單個* (a** <=> a* 一個道理)

 v0.3版本

``` 省略
    nPattern := make([]rune, 0, len(p))
    flag := false
    for _, v := range p {
        if v == '*' && flag {
            continue
        }
        if v == '*' {
            flag = true
        } else {
            flag = false
        }
        nPattern = append(nPattern, v)
    }
    return sMatch(nPattern, []rune(s))
```省略

想着這回應該OK了吧(真是有夠折磨人的,多虧了好case不然就沒有下面的優化了)

case: 這裏改一下,timeout了(啥!!!!)

str: "abbabaaabbabbaababbabbbbbabbbabbbabaaaaababababbbabababaabbababaabbbbbbaaaabababbbaabbbbaabbbbababababbaabbaababaabbbababababbbbaaabbbbbabaaaabbababbbbaababaabbababbbbbababbbabaaaaaaaabbbbbaabaaababaaaabb"
pattern: "**aa*****ba*a*bb**aa*ab****a*aaaaaa***a*aaaa**bbabb*b*b**aaaaaaaaa*a********ba*bbb***a*ba*bb*bb**a*b*bb"

 終於我又發現(我其實一開始就知道,只是不知道如何表現),pattern中非*的子串必定存在於str中,不然必定匹配失敗!

v0.4版本 誕生

func prepareMatch(pattern, str string) bool {
    p1 := strings.Replace(pattern, "?", "*", len(pattern))
    p2 := strings.Split(p1, "*")
    if len(p2) > 1 {
        for _, v := range p2 {
            if v == "" {
                continue
            }
            if strings.Count(p1, v) > strings.Count(str, v) {
                return false
            }
        }
    }
    return true
}


```省略
    if !prepareMatch(p, s) {
        return false
    }
    return sMatch(nPattern, []rune(s))
```省略

 其實這一版的優化有點小瑕疵,優化的大概意思是pattern中非*?的子串出現的次數應該等於它們在str中出現的次數,但是我忽略了它們之前的出現順序,所以又一個case讓我timeout了

str: "aaaaaabbaabaaaaabababbabbaababbaabaababaaaaabaaaabaaaabababbbabbbbaabbababbbbababbaaababbbabbbaaaaaaabbaabbbbababbabbaaabababaaaabaaabaaabbbbbabaaabbbaabbbbbbbaabaaababaaaababbbbbaabaaabbabaabbaabbaaaaba"
pattern: "*bbb**a*******abb*b**a**bbbbaab*b*aaba*a*b**a*abb*aa****b*bb**abbbb*b**bbbabaa*b**ba**a**ba**b*a*a**aaa"

 不知道看到這個case的各位,有沒有發現pattern和str的末尾字串並不匹配,但是因爲匹配順序是從前往後,即使最終匹配失敗,程序還是會傻呼呼的嘗試完所有可能,因此v0.5終於誕生了(順便修復了v0.4版本的小瑕疵)。

v0.5版本(終焉版本)

func prepareMatch(pattern, str string) bool {
    p1 := strings.Replace(pattern, "?", "*", len(pattern))
    p2 := strings.Split(p1, "*")
    cs := str
    //第一步預處理
    if len(p2) > 1 {
        for _, v := range p2 {
            if v == "" {
                continue
            }
            if i := strings.Index(cs, v); i < 0 {
                return false
            } else {
                cs = cs[i+len(v):]
            }
        }
    }
    //第二步預處理
    if pattern[len(pattern)-1] != '*' && len(p2) > 2{
        if !strings.HasSuffix(str, p2[len(p2)-1]) {
            return false;
        }
    }
    return true
}

優化了v0.4出現的兩個問題

編程的樂趣可能還是 不斷解決程序運行的問題 的過程!

完整終焉版本

import "strings"

func sMatch(pattern, str []rune) bool {
    if len(str) <= 0 {
        if len(pattern) <= 0 {
            return true
        }
        for _, c := range pattern {
            if c != '*' {
                return false
            }
        }
        return true
    }
    if len(pattern) <= 0 {
        return false
    }
    sl := len(str)
    pIndex := 0
    i := 0
    for i = 0; i < sl; i++ {
        if pIndex >= len(pattern) {
            break
        }
        switch pattern[pIndex] {
        case '*':
            gl := i
            flag := false
            for gl < sl{
                if pIndex == len(pattern)-1 {
                    return true
                }
                if sMatch(pattern[pIndex+1:], str[gl:]) {
                    flag = true
                    break
                }
                gl++ //要一個
            }
            return flag
        case '?':
            pIndex++
        default:
            if str[i] != pattern[pIndex] {
                return false
            }
            pIndex++
        }
    }
    if i == len(str) && pIndex != len(pattern) {
        for i := pIndex; i < len(pattern); i++ {
            if pattern[i] != '*' {
                return false
            }
        }
        return true
    }
    if pIndex != len(pattern) || i != len(str) {
        return false
    }
    return true
}

//優化
func prepareMatch(pattern, str string) bool {
    p1 := strings.Replace(pattern, "?", "*", len(pattern))
    p2 := strings.Split(p1, "*")
    cs := str
    //第一步預處理
    if len(p2) > 1 {
        for _, v := range p2 {
            if v == "" {
                continue
            }
            if i := strings.Index(cs, v); i < 0 {
                return false
            } else {
                cs = cs[i+len(v):]
            }
        }
    }
    //第二步預處理
    if pattern[len(pattern)-1] != '*' && len(p2) > 2{
        if !strings.HasSuffix(str, p2[len(p2)-1]) {
            return false;
        }
    }
    return true
}

func isMatch(s string, p string) bool {
    if len(s) <= 0 {
        if len(p) <= 0 {
            return true
        }
        for _, c := range p {
            if c != '*' {
                return false
            }
        }
        return true
    }
    if len(p) <= 0 {
        return false
    }
    nPattern := make([]rune, 0, len(p))
    flag := false
    for _, v := range p {
        if v == '*' && flag {
            continue
        }
        if v == '*' {
            flag = true
        } else {
            flag = false
        }
        nPattern = append(nPattern, v)
    }
    if !prepareMatch(p, s) {
        return false
    }
    return sMatch(nPattern, []rune(s))
}

 

發佈了34 篇原創文章 · 獲贊 3 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章