本文最后更新于:2 个月前

一、字典树

字典树(Trie) 又称单词查找树或键树,是一种哈希树的变种。典型的应用是用于统计和排序大量的字符串(但不限于字符串),优点是可以可以最大限度地减少无畏的字符串比较,查询效率比哈希表高。Trie的核心思想是空间换时间,利用字符串的公共前缀来降低查询的时间。

比如有一个包含多个单词的列表:[‘word’, ‘work’, ‘code’, ‘coffe’],可以下面的字典树表示:

graph TD
root((root)) -- w --> w(( ))
w -- o --> o(( ))
o -- r --> r(( ))
r -- d --> d(( ))
r -- k --> k(( ))

root -- c --> c(( ))
c -- o --> o2(( ))
o2 -- d --> d2(( ))
d2 -- e --> e(( ))
o2 -- f --> f(( ))
f -- f --> f2(( ))
f2 -- e --> e2(( ))
style root fill:#f9f
style d fill:#f9f
style k fill:#f9f
style e fill:#f9f
style e2 fill:#f9f

字典树的性质:

  • 根节点不包含字符,除根节点外每个节点只包含一个字符
  • 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串
  • 每个节点的所有子节点包含的字符串都不同

从头实现字符串(leetcode第208题):
实现一个 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 构成的;保证所有输入均为非空字符串。

class Trie:
    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.root = {}
        self.end_of_word = '#'


    def insert(self, word: str) -> None:
        """
        Inserts a word into the trie.
        """
        node = self.root
        for c in word:
            node = node.setdefault(c, {})
        node[self.end_of_word] = 1

    def search(self, word: str) -> bool:
        """
        Returns if the word is in the trie.
        """
        node = self.root
        for c in word:
            if c not in node:
                return False
            node = node[c]
        return self.end_of_word in node


    def startsWith(self, prefix: str) -> bool:
        """
        Returns if there is any word in the trie that starts with the given prefix.
        """
        node = self.root
        for c in prefix:
            if c not in node:
                return False
            node = node[c]
        return True

上面是比较造轮子的一种方法,而在python里可以直接用字典来实现字典树

二、例题

(1)单词搜索II

此题为leetcode第212题
给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。

思路:字典树+深度优先搜索

# 方向数组
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]
# word结束标记
end_of_word = '#'

class Solution:
    def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
        if not board or len(board[0]) == 0 or len(words) == 0:
            return []
        
        self.res = set()    # 结果保存在集合中
        root = {}   # 初始化根节点为空字典
        # 对列表里的word构建字典树
        for word in words:
            node = root
            # 这里的root将会是一个字典层层嵌套的结构。
            # 如果c在node里,则取出键为c的值,如果不在,则在键c下新建空字典
            for c in word:
                node = node.setdefault(c, {})   
            node[end_of_word] = end_of_word     # 标记word已结束
        
        self.m, self.n = len(board), len(board[0])
        
        # 遍历board,进行DFS
        for i in range(self.m):
            for j in range(self.n):
                if board[i][j] in root:
                    self._dfs(board, i, j, '', root)
        
        return list(self.res)
    
    def _dfs(self, board, i, j, curr_word, curr_dict):
        curr_word += board[i][j]
        curr_dict = curr_dict[board[i][j]]
        
        if end_of_word in curr_dict:
            self.res.add(curr_word)
        
        temp, board[i][j] = board[i][j], '*'    # '*'表示暂时标记为已访问过
        # 在(i, j)的4个方向上遍历
        for k in range(4):
            x, y = i + dx[k], j + dy[k]
            # 如果x、y没有越界,并且没有被访问过,并且当前字母在curr_dict中
            if 0 <= x < self.m and 0 <= y < self.n and board[x][y] != '*' and board[x][y] in curr_dict:
                self._dfs(board, x, y, curr_word, curr_dict)
        board[i][j] = temp  # 恢复board[i][j]的值