小秋今天去面試了,面試官問了一個與敏感詞過濾算法相關的問題,然而小秋對敏感詞過濾算法一點也沒據說過。因而,有了如下事情的發生…..面試
面試官:玩過王者榮耀吧?瞭解過敏感詞過濾嗎?,例如在遊戲裏,若是咱們發送「你在幹嗎?麻痹演員啊你?」,因爲「麻痹」是一個敏感詞,因此當你把聊天發出來以後,咱們會用「**」來表明「麻痹」此次詞,因此發送出來的聊天會變成這樣:「你在幹嗎?**演員啊你?」。算法
小秋:據說過啊,在各大社區也常常看到,例如評論一個問題等,一些粗話常常被過濾掉了。數據結構
面試官:嗯,若是我給你一段文字,以及給你一些須要過濾的敏感詞,你會怎麼來實現這個敏感詞過濾的算法呢?例如我給你一段字符串「abcdefghi",以及三個敏感詞"de", "bca", "bcf"。3d
小秋:(敏感詞過來算法??不就是字符串匹配嗎?)我能夠經過字符串匹配算法,例如在字符串」abcdefghi"在查找是否存在字串「de",若是找到了就把」de「用""代替。經過三次匹配以後,接變成這樣了:「abcfghi"。指針
面試官:能夠說說你採用哪一種字符串匹配算法嗎?blog
小秋:最簡單的方法就是採用兩個for循環保留求解了,不過每次匹配的都時間複雜度爲O(n*m),我能夠採用 KMP 字符串匹配算法,這樣時間複雜度是 O(m+n)。遊戲
n 表示字符串的長度,m 表示每一個敏感詞的長度。字符串
面試官:這是一個方法,對於敏感詞過濾,你還有其餘方法嗎?for循環
小秋:(其餘方法?說實話,我也以爲不是採用這種 KMP 算法來匹配的了,但是,以前也沒去了解過敏感詞,這下要涼)對敏感詞過來以前也沒了解過,暫時沒想到其餘方法。社區
面試官:瞭解過 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 來實現,你知道怎麼實現嗎?
小秋:(果真,面試官真是個好人啊,直接提示了,要是還不知道怎麼實現,那不真涼?)我想一想……..我知道了,我能夠這樣來實現:
先把你給個人三個敏感詞:"de", "bca", "bcf" 創建一顆 trie 樹,以下:
接着咱們能夠採用三個指針來遍歷,我直接用上面你給你例子來演示吧。
一、首先指針 p1 指向 root,指針 p2 和 p3 指向字符串第一個字符
二、而後從字符串的 a 開始,檢測有沒有以 a 做爲前綴的敏感詞,直接判斷 p1 的孩子節點中是否有 a 這個節點就能夠了,顯然這裏沒有。接着把指針 p2 和 p3 向右移動一格。
三、而後從字符串 b 開始查找,看看是否有以 b 做爲前綴的字符串,p1 的孩子節點中有 b,這時,咱們把 p1 指向節點 b,p2 向右移動一格,不過,p3不動。
四、判斷 p1 的孩子節點中是否存在 p2 指向的字符c,顯然有。咱們把 p1 指向節點 c,p2 向右移動一格,p3不動。
五、判斷 p1 的孩子節點中是否存在 p2 指向的字符d,這裏沒有。這意味着,不存在以字符b做爲前綴的敏感詞。這時咱們把p2和p3都移向字符c,p1 仍是還原到最開始指向 root。
六、和前面的步驟同樣,判斷有沒以 c 做爲前綴的字符串,顯然這裏沒有,因此把 p2 和 p3 移到字符 d。
七、而後從字符串 d 開始查找,看看是否有以 d 做爲前綴的字符串,p1 的孩子節點中有 d,這時,咱們把 p1 指向節點 b,p2 向右移動一格,不過,p3和剛纔同樣不動。(看到這裏,我猜你已經懂了)
八、判斷 p1 的孩子節點中是否存在 p2 指向的字符e,顯然有。咱們把 p1 指向節點 e,而且,這裏e是最後一個節點了,查找結束,因此存在敏感詞de,即 p3 和 p2 這個區間指向的就是敏感詞了,把 p2 和 p3 指向的區間那些字符替換成 *。而且把 p2 和 p3 移向字符 f。以下:
九、接着仍是重複一樣的步驟,知道 p3 指向最後一個字符。
面試官:能夠說說時間複雜度嗎?
小秋:若是敏感詞的長度爲 m,則每一個敏感詞的查找時間複雜度是 O(m),字符串的長度爲 n,咱們須要遍歷 n 遍,因此敏感詞查找這個過程的時間複雜度是 O(n * m)。若是有 t 個敏感詞的話,構建 trie 樹的時間複雜度是 O(t * m)。
這裏我說明一下,在實際的應用中,構建 trie 樹的時間複雜度我以爲能夠忽略,由於 trie 樹咱們能夠在一開始就構建了,之後能夠無數次重複利用的了。
十、若是讓你來 構建 trie 樹,你會用什麼數據結構來實現?
小秋:我通常使用 Java,我會採用 HashMap 來實現,由於一個節點的字節點個數未知,採用 HashMap 能夠動態拓展,並且能夠在 O(1) 複雜度內判斷某個子節點是否存在。
面試官:嗯,回去等通知吧。
今天主要將了 trie 樹以及 trie 樹的一些應用,還要就是如何經過 trie 樹來實現敏感詞的過濾,至於代碼的實現,我這裏就不給出了,在實現的時候,爲了防止這種」麻 痹"或者「麻¥痹」等,咱們也要對特殊字符進行過濾等,有興趣的能夠去實現一波。