208 实现 Tire(前缀树) Medium **Trie **(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。
请你实现 Trie 类:
Trie()
初始化前缀树对象。
void insert(String word)
向前缀树中插入字符串 word
。
boolean search(String word)
如果字符串 word
在前缀树中,返回 true
(即,在检索之前已经插入);否则,返回 false
。
boolean startsWith(String prefix)
如果之前已经插入的字符串 word
的前缀之一为 prefix
,返回 true
;否则,返回 false
。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 输入 ["Trie", "insert", "search", "search", "startsWith", "insert", "search"] [[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]] 输出 [null , null , true , false , true , null , true ] 解释 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
提示:
1 <= word.length, prefix.length <= 2000
word
和 prefix
仅由小写英文字母组成
insert
、search
和 startsWith
调用次数 总计 不超过 3 * 104
次
思路 首先需要解释下什么是Tire结构,该结构的每个几点存储了对其子节点的链接,每个节点代表一个字符。根节点不包含字符,除根节点外的每个节点都与一个字符相关联。除此之外,节点通常还包含一个标志,表示该结点是否是某个字符串的结束。
初始化 使用字典去存储,并用一个bool型变量去标记是否是单词的结尾
插入字符 从根节点开始遍历,如果不存在就创建一个新的子节点。遍历完成后标记最后一个节点为字符串的结束。
搜索字符串 这里和前面一样,只不过存在返回一个True不存在返回False,但需要保证最后一个节点被标记为字符串的结束
前缀搜索 能够完整遍历,但不需要检查最后一个节点是否被标记为结束
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Trie : def __init__ (self ): self .children = {} self .is_end_of_word = False def insert (self, word: str ) -> None : current = self for char in word: if char not in current.children: current.children[char] = Trie() current = current.children[char] current.is_end_of_word = True def search (self, word: str ) -> bool : current = self for char in word: if char not in current.children: return False current = current.children[char] return current.is_end_of_word def startsWith (self, prefix: str ) -> bool : current = self for char in prefix: if char not in current.children: return False current = current.children[char] return True
时间复杂度 时间复杂度均为O(n),空间复杂度为O(m * n),执行时间133ms,消耗内存31.3MB
211 添加与搜索单词 - 数据结构设计 Medium 请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。
实现词典类 WordDictionary
:
WordDictionary()
初始化词典对象
void addWord(word)
将 word
添加到数据结构中,之后可以对它进行匹配
bool search(word)
如果数据结构中存在字符串与 word
匹配,则返回 true
;否则,返回 false
。word
中可能包含一些 '.'
,每个 .
都可以表示任何一个字母。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 输入: ["WordDictionary","addWord","addWord","addWord","search","search","search","search"] [[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]] 输出: [null ,null ,null ,null ,false ,true ,true ,true ] 解释: WordDictionary wordDictionary = new WordDictionary(); wordDictionary.addWord("bad"); wordDictionary.addWord("dad"); wordDictionary.addWord("mad"); wordDictionary.search ("pad"); // 返回 False wordDictionary.search ("bad"); // 返回 True wordDictionary.search (".ad"); // 返回 True wordDictionary.search ("b.."); // 返回 True
提示:
1 <= word.length <= 25
addWord
中的 word
由小写英文字母组成
search
中的 word
由 ‘.’ 或小写英文字母组成
最多调用 104
次 addWord
和 search
思路 和前一题的思路基本上一致,但由于在 search
中我们需要分辨含有 .
的特殊节点,所以我们采用dfs去做,如果我们能够匹配到就返回,不能满足就下一个,所有都不满足就False
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Node : def __init__ (self ): self .children = [None ] * 26 self .isEnd = False class WordDictionary : def __init__ (self ): self .root = Node() def addWord (self, word: str ) -> None : node = self .root for char in word: index = ord (char) - ord ('a' ) if not node.children[index]: node.children[index] = Node() node = node.children[index] node.isEnd = True def search (self, word: str ) -> bool : def dfs (word, index, root ): if not root: return False if index == len (word): return root.isEnd if word[index] != '.' : i = ord (word[index]) - ord ('a' ) return dfs(word, index + 1 , root.children[i]) else : for child in root.children: if dfs(word, index + 1 , child): return True return False return dfs(word, 0 , self .root)
时间复杂度 时间复杂度 addWord
为O(n),search
最坏是O(n * 26),最好是O(n),执行时间为1916ms,消耗内存75.7MB
212 单词搜索 II Hard 给定一个 m x n
二维字符网格 board
和一个单词(字符串)列表 words
, 返回所有二维网格上的单词 。
单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
示例 1:
1 2 输入:board = [["o" ,"a" ,"a" ,"n" ],["e" ,"t" ,"a" ,"e" ],["i" ,"h" ,"k" ,"r" ],["i" ,"f" ,"l" ,"v" ]], words = ["oath" ,"pea" ,"eat" ,"rain" ] 输出:["eat" ,"oath" ]
示例 2:
1 2 输入:board = [["a" ,"b" ],["c" ,"d" ]], words = ["abcb" ] 输出:[]
提示:
m == board.length
n == board[i].length
1 <= m, n <= 12
board[i][j]
是一个小写英文字母
1 <= words.length <= 3 * 104
1 <= words[i].length <= 10
words[i]
由小写英文字母组成
words
中的所有字符串互不相同
思路 首先建立一个 TrieNode,再构建一个 Trie 树
再构建一个DFS,遍历当前节点的四个方位,并存储到结果集中,最终转换为列表返回
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class TireNode : def __init__ (self ): self .children = {} self .is_end_of_word = False class Solution : def findWords (self, board: List [List [str ]], words: List [str ] ) -> List [str ]: trie = TireNode() for word in words: node = trie for char in word: if char not in node.children: node.children[char] = TireNode() node = node.children[char] node.is_end_of_word = True result = set () rows, cols = len (board), len (board[0 ]) def dfs (row, col, node, path ): if not (0 <= row < rows and 0 <= col < cols) or board[row][col] == '#' : return char = board[row][col] if char not in node.children: return child = node.children[char] if child.is_end_of_word: result.add(path + char) if not child.children: return board[row][col] = '#' dfs(row + 1 , col, child, path + char) dfs(row - 1 , col, child, path + char) dfs(row, col + 1 , child, path + char) dfs(row, col - 1 , child, path + char) board[row][col] = char for row in range (rows): for col in range (cols): dfs(row, col, trie, '' ) return list (result)
时间复杂度 时间复杂度O(N * L + M * N * 4^L),空间复杂度O(N * L + L + K),N
是单词数量,L
是单词的平均长度,M
和 N
是字符网格的行数和列数,K
是找到的单词数量。执行时间2852ms,消耗内存18.6MB
总结 这三道题我是推荐以第三题为主,因为他的整个流程更加全面,能够很好的对此类题构建出一个模板来,所以我这里也更推荐采用第三题作为主力研究对象。
我自己大致总结了一下做题的模板和方法:
操作实现方法 初始化 :
使用一个字典来存储子节点。
用一个布尔变量 is_end_of_word
标记当前节点是否是单词的结束节点。
插入操作 :
从根节点开始,逐字符检查字典树中是否存在对应的子节点。
如果不存在,则创建一个新节点;遍历完所有字符后,将最后一个节点标记为单词结束。
搜索操作 :
从根节点逐字符遍历字典树。如果找到对应节点且最终节点被标记为单词结束,则返回 True
;否则返回 False
。
前缀搜索操作 :
从根节点逐字符遍历字典树,只要能成功遍历完整个前缀即可返回 True
,无需检查最终节点是否为单词结束。
模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Trie : def __init__ (self ): self .children = {} self .is_end_of_word = False def insert (self, word: str ) -> None : current = self for char in word: if char not in current.children: current.children[char] = Trie() current = current.children[char] current.is_end_of_word = True def search (self, word: str ) -> bool : current = self for char in word: if char not in current.children: return False current = current.children[char] return current.is_end_of_word def startsWith (self, prefix: str ) -> bool : current = self for char in prefix: if char not in current.children: return False current = current.children[char] return True
整体上来看,都是基于字典树的基本结构,操作上主要是通过遍历和检查节点是否存在来完成插入、查找和前缀匹配的功能。
同时可以看到核心代码几乎完全复用,体现了字典树操作的模块化与通用性。
在这里还需要补充一个点,ord
函数。该函数的作用是将单个字符转换为其对应的Unicode整数表示。这个函数的功能可以概括如下:
字符到整数的转换:ord()
函数接受一个单个字符作为输入,并返回该字符的Unicode码点(整数值)
Unicode支持:ord()
函数可以处理任何Unicode字符,不仅限于ASCII字符
单字符输入:ord()
函数只接受单个字符作为参数。如果尝试传入多个字符,会导致错误