【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

帥地發表於2019-05-08

版權宣告:本文為苦逼的碼農原創。未經同意禁止任何形式轉載,特別是那些複製貼上到別的平臺的,否則,必定追究。歡迎大家多多轉發,謝謝。

小秋今天去面試了,面試官問了一個與敏感詞過濾演算法相關的問題,然而小秋對敏感詞過濾演算法一點也沒聽說過。於是,有了下下事情的發生.....

面試官開懟

面試官:玩過王者榮耀吧?瞭解過敏感詞過濾嗎?,例如在遊戲裡,如果我們傳送“你在幹嘛?麻痺演員啊你?”,由於“麻痺”是一個敏感詞,所以當你把聊天發出來之後,我們會用“**”來代表“麻痺”這次詞,所以傳送出來的聊天會變成這樣:“你在幹嘛?**演員啊你?”。

小秋:聽說過啊,在各大社群也經常看到,例如評論一個問題等,一些粗話經常被過濾掉了。

面試官:嗯,如果我給你一段文字,以及給你一些需要過濾的敏感詞,你會怎麼來實現這個敏感詞過濾的演算法呢?例如我給你一段字串“abcdefghi",以及三個敏感詞"de", "bca", "bcf"。

小秋:(敏感詞過來演算法??不就是字串匹配嗎?)我可以通過字串匹配演算法,例如在字串”abcdefghi"在查詢是否存在字串“de",如果找到了就把”de“用""代替。通過三次匹配之後,接變成這樣了:“abc fghi"。

面試官:可以說說你採用哪種字串匹配演算法嗎?

小秋:最簡單的方法就是採用兩個for迴圈保留求解了,不過每次匹配的都時間複雜度為O(n*m),我可以採用 KMP 字串匹配演算法,這樣時間複雜度是 O(m+n)。

n 表示字串的長度,m 表示每個敏感詞的長度。

面試官:這是一個方法,對於敏感詞過濾,你還有其他方法嗎?

小秋:(其他方法?說實話,我也覺得不是採用這種 KMP 演算法來匹配的了,可是,之前也沒去了解過敏感詞,這下要涼)對敏感詞過來之前也沒了解過,暫時沒想到其他方法。

trie 樹

面試官:瞭解過 trie 樹嗎?

小秋:(嘿嘿,資料結構這方法,我得爭氣點)瞭解過,我還用程式碼實現過。

面試官:可以說說它的特點嗎?

小秋:trie 樹也稱為字典樹、單詞查詢樹,最大的特點就是共享字串的公共字首來達到節省空間的目的了。例如,字串 "abc"和"abd"構成的 trie 樹如下:

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

trie 樹的根節點不存任何資料,每整個個分支代表一個完整的字串。像 abc 和 abd 有公共字首 ab,所以我們可以共享節點 ab。如果再插入 abf,則變成這樣:

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

如果我再插入 bc,則是這樣(bc 和其他三個字串沒有公共字首)

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

面試官:那如果再插入 "ab" 這個字串呢?

小秋:差點說了,每個分支的內部可能也含有完整的字串,所以我們可以對於那些是某個字串結尾的節點做一個標記,例如 abc, abd,abf 都包含了字串 ab,所以我們可以在節點 b 這裡做一個標記。如下(我用紅色作為標記):

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

面試官:可以說說 trie 樹有哪些應用嗎?

小秋:trie 最大的特點就是利用了字串的公共字首,像我們有時候在百度、谷歌輸入某個關鍵字的時候,它會給我們列舉出很多相關的資訊

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

這種就是通過 trie 樹來實現的。

小秋:(嗯? trie 又稱為單詞查詢樹,好像可以用 trie 來實現剛才的敏感詞匹配?面試官無緣無故提 trie 樹難道別有用意?)

面試官:剛才的敏感詞過濾,其實也可以採用 trie 來實現,你知道怎麼實現嗎?

trie 樹來實現敏感詞過濾

小秋:(果然,面試官真是個好人啊,直接提示了,要是還不知道怎麼實現,那不真涼?)我想想........我知道了,我可以這樣來實現:

先把你給我的三個敏感詞:"de", "bca", "bcf" 建立一顆 trie 樹,如下:

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

接著我們可以採用三個指標來遍歷,我直接用上面你給你例子來演示吧。

1、首先指標 p1 指向 root,指標 p2 和 p3 指向字串第一個字元

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

2、然後從字串的 a 開始,檢測有沒有以 a 作為字首的敏感詞,直接判斷 p1 的孩子節點中是否有 a 這個節點就可以了,顯然這裡沒有。接著把指標 p2 和 p3 向右移動一格。

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

3、然後從字串 b 開始查詢,看看是否有以 b 作為字首的字串,p1 的孩子節點中有 b,這時,我們把 p1 指向節點 b,p2 向右移動一格,不過,p3不動。

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

4、判斷 p1 的孩子節點中是否存在 p2 指向的字元c,顯然有。我們把 p1 指向節點 c,p2 向右移動一格,p3不動。

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

5、判斷 p1 的孩子節點中是否存在 p2 指向的字元d,這裡沒有。這意味著,不存在以字元b作為字首的敏感詞。這時我們把p2和p3都移向字元c,p1 還是還原到最開始指向 root。

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

6、和前面的步驟一樣,判斷有沒以 c 作為字首的字串,顯然這裡沒有,所以把 p2 和 p3 移到字元 d。

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

7、然後從字串 d 開始查詢,看看是否有以 d 作為字首的字串,p1 的孩子節點中有 d,這時,我們把 p1 指向節點 b,p2 向右移動一格,不過,p3和剛才一樣不動。(看到這裡,我猜你已經懂了)

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

8、判斷 p1 的孩子節點中是否存在 p2 指向的字元e,顯然有。我們把 p1 指向節點 e,並且,這裡e是最後一個節點了,查詢結束,所以存在敏感詞de,即 p3 和 p2 這個區間指向的就是敏感詞了,把 p2 和 p3 指向的區間那些字元替換成 *。並且把 p2 和 p3 移向字元 f。如下:

【面試被虐】說說遊戲中的敏感詞過濾是如何實現的?

9、接著還是重複同樣的步驟,知道 p3 指向最後一個字元。

複雜度分析

面試官:可以說說時間複雜度嗎?

小秋:如果敏感詞的長度為 m,則每個敏感詞的查詢時間複雜度是 O(m),字串的長度為 n,我們需要遍歷 n 遍,所以敏感詞查詢這個過程的時間複雜度是 O(n * m)。如果有 t 個敏感詞的話,構建 trie 樹的時間複雜度是 O(t * m)。

這裡我說明一下,在實際的應用中,構建 trie 樹的時間複雜度我覺得可以忽略,因為 trie 樹我們可以在一開始就構建了,以後可以無數次重複利用的了。而剛才的 kmp 演算法時間複雜度是 t *(m+n),不過kmp需要維護 next 陣列比較費空間,而且在實際情況中,敏感詞的數量 t 是比較大,而 n 反而比較小的吧。

10、如果讓你來 構建 trie 樹,你會用什麼資料結構來實現?

小秋:我一般使用 Java,我會採用 HashMap 來實現,因為一個節點的位元組點個數未知,採用 HashMap 可以動態擴充,而且可以在 O(1) 複雜度內判斷某個子節點是否存在。

面試官:嗯,回去等通知吧。

總結

今天主要將了 trie 樹以及 trie 樹的一些應用,還要就是如何通過 trie 樹來實現敏感詞的過濾,至於程式碼的實現,我這裡就不給出了,在實現的時候,為了防止這種”麻 痺"或者“麻¥痺”等,我們也要對特殊字元進行過濾等,有興趣的可以去實現一波。

今天也是第一次嘗試採用這種對話的方式來寫文章,可能寫的沒有平常的好,不過我會慢慢改進,希望大家多多支援。

最後推薦下我的公眾號:苦逼的碼農,主要分享一下技術文章、面試題、演算法題,各種工具、視訊資源等,裡面已有100多篇原創文章,期待各路英雄來交流,點選即可掃碼關注戳我即可關注

相關文章