close

今天要來解的題目是 Leetcode-208. Implement Trie (Prefix Tree),難度為中等

而我所使用的語言是 python3

壹、理解題目

Implement a trie with insert, search, and startsWith methods.

Example:

Trie trie = new Trie();

trie.insert("apple");
trie.search("apple");   // returns true
trie.search("app");     // returns false
trie.startsWith("app"); // returns true
trie.insert("app");   
trie.search("app");     // returns true

所以說,什麼是 Trie (pronounce "try") 啊?

貳、Prefix Tree - Trie

Trie 又稱作 prefix tree,

之所以取作 Trie 是因為該資料結構能很有效率的取得(reTrieval)資訊,

有什麼用? Google 的建議查詢就是應用到這個資料結構!

image

多說無益,直接看圖

image

像是上圖中的 Trie,共有五個單詞(深黃色的圈代表字尾),分別是 app, apple, car, card, care,

到這裡,你應該能夠推論出,每一個節點(node)最多就只會有 26 個子節點(child node),

因為我們忽略大小寫,所以每個節點所儲存的字元(character)也就只可能有 26 種。

 

來分析一下時間複雜度吧,

「搜尋/插入/刪除一個單詞」的時間複雜度會直接和該單詞的長度有關係,不難理解,

如果你要找 'apple' 這個單詞(由五個字母組成),你勢必要往下走五個 node 才能找到吧!

所以,時間複雜度 = O(M),M = 字串長度;

是線性的耶?!這顯然比 BST (binary search tree) 快,

沒錯,但 Trie 所付出的代價是空間的成本,

因為對於任一一個節點,都有 26 種可能,這樣 26x26x26....下去,

如果今天有非常多單詞,這個 Trie 會超龐大!

 

參、實作 Trie

這題要實作的 function 有 insert, search, 和 startsWith,不用處理刪除,

下圖是 Leetcode 提供的樹幹,要你填滿它!

image

再看一次上面的 Trie 圖,

顯然 Trie 是由 node 組成的,所以應該創立一個 class

image

Node 只有兩種,淺黃/深黃色,深黃色代表一個完整的單詞的字尾,

在這裡,我用 is_leave 來表示,因為 leave 在 Tree 的資料結構中通常代表「沒有子節點 的 節點(node)」

self.children 先初始設定為 [None] * 26,因為每個 node 最多就只可能有 26 個小孩嘛~

 

接著來設計 Trie 的資料結構!

在 __init__() 中,就只要一行,代表此時的 Trie 只有 root,裡面還沒有插入任何單詞

image

雖然在 Trie 圖中,每個 node 裡面放的是「小寫字母」,

但為了方便起見,可以把 'a' 對應到 0,'b' 對應到 1,... 'z'對應到 25,

如此一來,TrieNode 的 children 才能用 index 去作搜尋,例如:node.children[0] 就代表 'a' 這個 子node,

而這樣的 mapping 可以用一個 function 實現,同樣只要一行就搞定~

image

 

肆、實作 function "insert"

接著,來到第一個 function 的實作-insert

你會怎麼把一個單詞 (word) 插入樹 (Trie) 呢?

一定是從 root 開始往下找吧!

 

假設,今天我們想要插入 "car" 這個單詞到下面的樹中,

那我們先站在 root 這個節點上,往下看,沒有找到 'c',

 

顯然,你需要新增一個 child node 'c' 在 root 之下,同時跳到 'c' 這個節點上,

以此類推,放入 'a' 和 'r',

最後要記得把最後一個字母 'r' 上色,也就是標示 is_leaf = True,

這樣之後在作搜尋的時候,程式才會知道來到最後一個字母了~

image     image    image    image    image

那如果今天要接續插入的字是 'card' 呢?顯然不用再放入 'car' 吧!

所以程式要作檢查,確認子節點是否存在,

也就是站在 'c' node 的時候,想要放入 'a',那要先檢查 'c' 的子節點中有沒有包含 'a'?

若有,就直接移動到 'a' 節點,若沒有,要先創立好 'a' 節點,才可以跳過去。

(好像在闖關/造路)

image   image    image     image   image     image

程式碼如下,

 image

 

伍、實作 function "search"

接著是第二個 function-search的實作!

搜尋單詞是否存在於 Trie 之中,有兩種 case:

  1. 如果今天要找 'ball',站在 root 節點,第一個字母 'b' 就已經不存在了,那何必繼續往下找呢?
  2. 今天要找 'ca' 這個詞,但 'a' 並沒有被標記成深黃色,這種情況也不行

所以,要 return True,你勢必要掃過整個詞,並且確認最後一個字母的 node 是被標記成深黃色的

image

 

陸、實作 function "startsWith"

最後是第三個 function-startsWith的實作!

這其實跟上一個 function 超像,甚至更簡單,因為這個 function 的概念是在找 prefix,

所以要 return True,你只需要掃過整個詞就好,不用確認最後一個字母的 node 是不是 leave (深黃色)

image

 

結果:Accepted!

image

創作者介紹
創作者 Sofie 舒霏 的頭像
Sofie 舒霏

Sofie 舒霏的部落格

Sofie 舒霏 發表在 痞客邦 留言(0) 人氣()