实现 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)