實現 Trie (前綴樹)
實現一個 Trie (前綴樹),包含 insert, search, 和 startsWith 這三個操作。
示例:
Trie trie = new Trie();
trie.insert(“apple”);
trie.search(“apple”); // 返回 true
trie.search(“app”); // 返回 false
trie.startsWith(“app”); // 返回 true
trie.insert(“app”);
trie.search(“app”); // 返回 true
說明:
你可以假設所有的輸入都是由小寫字母 a-z 構成的。
保證所有輸入均爲非空字符串。
解法:
記下前綴樹的實現:節點包括兩部分,一個是標誌,用來標記該結點處是否構成一個單詞(關鍵字);另一個是字典形式的子節點。
性質:根節點不包含字符,除根節點外的每一個子節點都包含一個字符;
從根節點到某一個節點,路徑上經過的字符連接起來,爲該節點對應的字符串;每個節點的所有子節點包含的字符互不相同。
核心思想:空間換時間,利用字符串的公共前綴來減少無謂的字符串比較以達到提高查詢效率的目的。
用途:字符串檢索;
詞頻統計——修改了節點結構,用一個整型變量count來計數。對每一個關鍵字執行插入操作,若已存在,計數加1,若不存在,插入後count置1。
字符串排序——遍歷一次所有關鍵字,將它們全部插入trie樹,樹的每個結點的所有兒子很顯然地按照字母表排序,然後先序遍歷輸出Trie樹中所有關鍵字即可。
前綴匹配——例如:找出一個字符串集合中所有以ab開頭的字符串。我們只需要用所有字符串構造一個trie樹,然後輸出以a->b->開頭的路徑上的關鍵字即可。
class Node:
def __init__(self):
self.word = False
self.child = {}
class Trie(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.root = Node()
def insert(self, word):
"""
Inserts a word into the trie.
:type word: str
:rtype: None
"""
cur = self.root
for w in word:
if w not in cur.child:
cur.child[w] = Node()
cur = cur.child[w]
cur.word = True
def search(self, word):
"""
Returns if the word is in the trie.
:type word: str
:rtype: bool
"""
cur = self.root
for w in word:
if w not in cur.child:
return False
cur = cur.child[w]
if cur.word == False:
return False
return True
def startsWith(self, prefix):
"""
Returns if there is any word in the trie that starts with the given prefix.
:type prefix: str
:rtype: bool
"""
cur = self.root
for w in prefix:
if w not in cur.child:
return False
cur = cur.child[w]
return True
# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)
單詞搜索 II
給定一個二維網格 board 和一個字典中的單詞列表 words,找出所有同時在二維網格和字典中出現的單詞。
單詞必須按照字母順序,通過相鄰的單元格內的字母構成,其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個單元格內的字母在一個單詞中不允許被重複使用。
示例:
輸入:
words = ["oath","pea","eat","rain"] and board =
[
['o','a','a','n'],
['e','t','a','e'],
['i','h','k','r'],
['i','f','l','v']
]
輸出: ["eat","oath"]
說明:
你可以假設所有輸入都由小寫字母 a-z 組成。
提示:
你需要優化回溯算法以通過更大數據量的測試。你能否早點停止回溯?
如果當前單詞不存在於所有單詞的前綴中,則可以立即停止回溯。什麼樣的數據結構可以有效地執行這樣的操作?散列表是否可行?爲什麼? 前綴樹如何?如果你想學習如何實現一個基本的前綴樹,請先查看這個問題: 實現Trie(前綴樹)。
解法:
首先顯然是要用到dfs,其次是用trie來空間換時間,可以提前結束回溯。
將words用前綴樹存儲起來;
ischecked數組用於dfs中不會出現重複搜索某個字符的情況;
注意數組越界問題,“上下左右”都要判斷;
如果是一個完整的單詞,添加該單詞,並將該單詞置爲空,避免重複
class Node:
def __init__(self):
self.word = ""
self.child = {}
class Solution(object):
def __init__(self):
self.root = Node()
self.res = []
def findWords(self, board, words):
"""
:type board: List[List[str]]
:type words: List[str]
:rtype: List[str]
"""
if not board or not words:
return []
col, row = len(board), len(board[0])
ischecked = [[False for i in range(row)] for j in range(col)]
self.createTree(words)
for i in range(col):
for j in range(row):
self.dfs(i, j, col, row, board, self.root, ischecked)
return self.res
def createTree(self, words):
for item in words:
cur = self.root
for w in item:
if w not in cur.child:
cur.child[w] = Node()
cur = cur.child[w]
cur.word = item
def dfs(self, i, j, col, row, board, node, ischecked):
if node.word != "":
self.res.append(node.word)
node.word = ""
if i<0 or i>=col or j<0 or j>=row or ischecked[i][j] == True:
return
if board[i][j] not in node.child:
return
cur = node.child[board[i][j]]
ischecked[i][j] = True
self.dfs(i+1, j, col, row, board, cur, ischecked)
self.dfs(i, j+1, col, row, board, cur, ischecked)
self.dfs(i-1, j, col, row, board, cur, ischecked)
self.dfs(i, j-1, col, row, board, cur, ischecked)
ischecked[i][j] = False
return
Map Sum Pairs
實現一個 MapSum 類裏的兩個方法,insert 和 sum。
對於方法 insert,你將得到一對(字符串,整數)的鍵值對。字符串表示鍵,整數表示值。如果鍵已經存在,那麼原來的鍵值對將被替代成新的鍵值對。
對於方法 sum,你將得到一個表示前綴的字符串,你需要返回所有以該前綴開頭的鍵的值的總和。
示例 1:
輸入: insert(“apple”, 3), 輸出: Null
輸入: sum(“ap”), 輸出: 3
輸入: insert(“app”, 2), 輸出: Null
輸入: sum(“ap”), 輸出: 5
解法
首先顯然是使用前綴樹保存字符串,node類除了有child字典,還有個value值。對於兩個方法,有兩種方式實現:
- insert就是正常的前綴樹插入,遇到完整的單詞時才保存value值,前綴的value都是none;在sum操作時,使用dfs遞歸遍歷每個有value的節點
- Insert的時候在每個經過的節點都保存了以此節點爲前綴的字符串的值的和。這樣,求和操作就變得很簡單,只需找出相應的前綴對應的和即可。
第一種:
class Node(object):
def __init__(self):
self.child = {}
self.num = None
class MapSum(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.root = Node()
self.res = 0
def insert(self, key, val):
"""
:type key: str
:type val: int
:rtype: None
"""
cur = self.root
for char in key:
if char not in cur.child:
cur.child[char] = Node()
cur = cur.child[char]
cur.num = val
def sum(self, prefix):
"""
:type prefix: str
:rtype: int
"""
self.res = 0
cur = self.root
for char in prefix:
if char not in cur.child:
return 0
else:
cur = cur.child[char]
self.cnt(cur)
return self.res
def cnt(self, cur):
if cur.num:
self.res += cur.num
for x in cur.child:
self.cnt(cur.child[x])
return
# Your MapSum object will be instantiated and called as such:
# obj = MapSum()
# obj.insert(key,val)
# param_2 = obj.sum(prefix)
第二種:
class Node(object):
def __init__(self, count = 0):
self.children = collections.defaultdict(Node)
self.count = count
class MapSum(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.root = Node()
self.keys = {}
def insert(self, key, val):
"""
:type key: str
:type val: int
:rtype: void
"""
curr = self.root
delta = val - self.keys.get(key, 0)
# 更新保存鍵值對的keys
self.keys[key] = val
curr = self.root
# 更新節點的count
curr.count += delta
for char in key:
curr = curr.children[char]
curr.count += delta
def sum(self, prefix):
"""
:type prefix: str
:rtype: int
"""
curr = self.root
for char in prefix:
if char not in curr.children:
return 0
curr = curr.children[char]
return curr.count
# Your MapSum object will be instantiated and called as such:
# obj = MapSum()
# obj.insert(key,val)
# param_2 = obj.sum(prefix)