青銅三人行之匹配子序列的單詞數

先說一個消息,爲了方便互相交流學習,青銅三人行建了個微信羣,感興趣的夥伴可以掃碼加下面的小助手抱你入羣哦!
青銅三人行小助手

每週一題,代碼無敵~這次讓我們換換口味,討論一個稍微偏實際一點的問題:

視頻

青銅三人行——每週一題@匹配子序列的單詞數

力扣題目鏈接

給定字符串 S 和單詞字典 words, 求 words[i] 中是 S 的子序列的單詞個數。

示例:

輸入: 
S = "abcde"
words = ["a", "bb", "acd", "ace"]
輸出: 3
解釋: 有三個是 S 的子序列的單詞: "a", "acd", "ace"。
注意:

所有在words和 S 裏的單詞都只由小寫字母組成。

S 的長度在 [1, 50000]。

words 的長度在 [1, 5000]。

words[i]的長度在[1, 50]

暴力破解法

這周的題目相對簡單。從題意上來理解,無非就是 words 數組中每一個單詞拿到 S 字符串中去嘗試匹配就好。稍微值得注意的是,因爲匹配的規則在於不一定是連續子字符串匹配,因此需要考慮每個字母在其中是否全部都存在,Helen 給出了暴力解法:

var numMatchingSubseq = function(S, words) {
    let count = 0;
    
    for (const word of words) {
        let index = -1;
        let _count = 0;
        for (const str of word) {            
            _count++;
            const _index = S.indexOf(str, index+1);

            if (_index === -1) {
                break;
            } else {
                index = _index;
            }

            if (_count === word.length) {
                count++;
            }
        }
    }
    return count;
};

書香在同樣的思路上,利用 JS 的自帶 API ,稍微做了一些寫法上的優化,讓程序看起來更簡短了一些:

var numMatchingSubseq = function (S, words) {
    const isSubWord = function (s, word) {
        let pos = -1;
        for (let i = 0; i < word.length; i++) {
            pos = s.indexOf(word[i], pos + 1);
            if (pos == -1) return 0;
        }
        return 1;
    };

    return words.reduce((count, word) => count + isSubWord(S, word), 0)
};

你看出了其中的相同之處了嗎?

正則表達式匹配

既然是字串匹配,自然可以通過正則表達式來完成匹配。書香嘗試了這一解法:

var numMatchingSubseq = function(S, words) {
    return words.reduce((count, word) => {
        const testReg = new RegExp(word.split('').join('\\w*'));
        if(testReg.test(S)) {
            count++;
        }
        return count;
    },0)
};

但是,正則匹配花費的計算資源會更高一些,因此這個解法在題目中的超長字串測試用例中,因爲超出時間限制而失敗了… 在這裏貼出這段代碼,僅作爲一種思路的參考。

可不可以不那麼暴力?

Helen 作爲三人行裏唯一的女生,自然忍不了動不動就「暴力破解」的做法 ‍♀️。因此她換了一個不難麼暴力的思路,通過將 words 中的單詞按照首字母先排序到一個「桶」中,將數據進行了預處理,然後在字符串匹配其中字符的的時候,就可以從對應的地方匹配了:

var numMatchingSubseq = function(S, words) {
    const bucket = Array.from({ length: 26 }, () => []);
    
    let count = 0;

    for (const word of words) {
        bucket[word.charCodeAt(0) - 97].push(word); // a的unicode是97
    }
    
    for (const str of S) {
        const list = bucket[str.charCodeAt(0) - 97];
        bucket[str.charCodeAt(0) - 97] = [];

        for (let word of list) {
            if (word.length > 1) {
                word = word.slice(1);
                if (word) {
                    bucket[word.charCodeAt(0) - 97].push(word);
                }
            } else {
                count++;
            }
        }
    }
    return count;
};

extra

曾大師的 go 語言時光,他似乎也很暴力 …

func numMatchingSubseq(S string, words []string) int {
    count := 0
    for i := 0; i < len(words); i++ {
        stat := 0
        word := words[i]
        if len(word) > len(S) {
            continue
        }else {
            for i := 0; i < len(S); i++ {
                if word[stat] == S[i]{
                    stat++
                    if stat == len(word) {
                        count++
                        break
                    }
                }
            } 
        }
    }
    return count
}

這一次他的時間消耗就沒有那麼好了,你能看出相比其前面 js 的兩個暴力解法,這次他爲什麼「失手」了嗎?

更多
在這次題目中,三人行不約而同採用了「暴力解法」,並且在一定程度的簡單優化上,時間和空間的利用成績都還不錯。事實在,在寫代碼的過程中,往往也是一個迭代的過程,前期過度地優化有時候反而不如先利用最直觀的方式把程序先跑起來,再根據需求和場景條件來進行對應的優化更好。

下週見啦~


三人行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章