676 实现一个魔法字典(Trie树、dfs)

9 篇文章 0 订阅

1. 问题描述: 

设计一个使用单词列表进行初始化的数据结构,单词列表中的单词互不相同 。 如果给出一个单词,请判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于你构建的字典中。
实现 MagicDictionary 类:
MagicDictionary() 初始化对象
void buildDict(String[] dictionary) 使用字符串数组 dictionary 设定该数据结构,dictionary 中的字符串互不相同
bool search(String searchWord) 给定一个字符串 searchWord ,判定能否只将字符串中一个字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回 true ;否则,返回 false 。

示例:

输入
["MagicDictionary", "buildDict", "search", "search", "search", "search"]
[[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]]
输出
[null, null, false, true, false, false]

解释

MagicDictionary magicDictionary = new MagicDictionary();
magicDictionary.buildDict(["hello", "leetcode"]);
magicDictionary.search("hello"); // 返回 False
magicDictionary.search("hhllo"); // 将第二个 'h' 替换为 'e' 可以匹配 "hello" ,所以返回 True
magicDictionary.search("hell"); // 返回 False
magicDictionary.search("leetcoded"); // 返回 False

提示:

1 <= dictionary.length <= 100
1 <= dictionary[i].length <= 100
dictionary[i] 仅由小写英文字母组成
dictionary 中的所有字符串互不相同
1 <= searchWord.length <= 100
searchWord 仅由小写英文字母组成
buildDict 仅在 search 之前调用一次
最多调用 100 次 search
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-magic-dictionary

2. 思路分析:

分析题目可以知道我们需要设计一个数据结构来存储所有的单词,并且这个数据结构能够支持查询操作,在查询单词的过程中能够判断出是否存在一个单词与当前查询的单词只存在一个字符是不同的。因为查询的时候我们需要判断出这个数据结构中是否只存在一个字符是不同的,所以我们想到Trie树来存储这些单词,并且Trie树能够支持查询所有单词的操作。Trie树在存储的这些单词的时候相当于是创建了一棵多叉树,每一个单词存在自己独立的分支,而且在查询单词的时候我们可以搜索整棵Trie树,这样就可以比较当前搜索的单词的字符与查询单词的字符是否相同。所以对于buildDict方法我们可以将所有的单词插入到Trie树中,对于search方法我们通过搜索Trie树中的所有单词(dfs),判断是否存在一个单词与当前单词只有一个字符,如果在搜索的过程中发现找到了符合的单词之后直接返回True。下面是使用字典来链接字符与字符之间关系创建的Trie树, 单词列表为["a", "b", "ab", "abc"]:

3. 代码如下:

from typing import List


class MagicDictionary:
    dic = None

    # Trie树的初始化
    def __init__(self):
        self.dic = dict()

    # 往Trie树中插入单词
    def insert(self, w: str):
        t = self.dic
        for c in w:
            if c not in t:
                t[c] = {}
            t = t[c]
        # 当前的单词结束标志
        t["end"] = 1

    # 调用Trie树的插入单词的函数往Trie树中插入所有的单词
    def buildDict(self, dictionary: List[str]) -> None:
        for w in dictionary:
            self.insert(w)

    # c表示当前Trie树中搜索的单词与查询单词的不同的字符数目, u表示当前searchWord的位置, t表示当前Trie树位于哪个层, t是一个字典
    def dfs(self, searchWord: str, c: int, u: int, t: dict):
        # 搜索到了Trie树中当前单词和searchword单词的末尾并且不同字符的个数为1
        if u == len(searchWord) and c == 1 and "end" in t: return True
        if c > 1 or u == len(searchWord): return False
        for k, v in t.items():
            # 因为当前的可能是某个单词的结束标记, 所以当前键是end的时候continue, 但是不能够直接返回False因为可能存在其他合法的分支
            if k == "end": continue
            if self.dfs(searchWord, c + 1 if k != searchWord[u] else c, u + 1, t[k]):  return True
        return False

    def search(self, searchWord: str) -> bool:
        t = self.dic
        return self.dfs(searchWord, 0, 0, t)


# if __name__ == '__main__':
#     obj = MagicDictionary()
#     obj.buildDict(["a", "b", "ab", "abc"])
#     res = obj.search("hello")
#     print(res)
#     res = obj.search("hhllo")
#     print(res)
#     res = obj.search("abb")
#     print(res)

使用idx唯一标识每一个节点:

from typing import List
class MagicDictionary:
    def __init__(self):
         # N为节点的个数, 因为单词的长度最大为100而且插入单词最多是100次, 所以节点个数最多为10000
         N = 10050
         # son表示Trie树, 第一维标识字符串前缀, 第二维标识当前前缀的最后一个字符
         son = [[0] * 26 for i in range(N)]
         self.son = son
         # idx用来标识每一个单词
         self.idx = 1
         # is_end用来记录单词的结尾标志
         is_end = [0] * N
         self.is_end = is_end
         
    def insert(self, w: str):
         son = self.son
         p = 0
         for c in w:
              k = ord(c) - ord("a")
              if son[p][k] == 0:
                   son[p][k] = self.idx
                   self.idx += 1
              p = son[p][k]
         self.is_end[p] = p
          
 
    def buildDict(self, dictionary: List[str]) -> None:
          for w in dictionary:
               self.insert(w)
    # k为当前递归的位置, count用来记录当前与prefix不同字符的个数, p表示Trie树节点的位置
    def dfs(self, prefix: str, k: int, count: int, p: int):
          if k == len(prefix) and count == 1 and self.is_end[p]: return True
          if count > 1 or k == len(prefix): return False
          for c in range(26):
               # 只有当当前位置大于0说明Trie树中存在当前的节点
               if self.son[p][c] > 0:
                    # 所有单词的字符都是小写字母, 当找到对应的满足要求的单词之后直接返回True
                    if self.dfs(prefix, k + 1, count + 1 if chr(c + 97) != prefix[k] else count, self.son[p][c]):  return True
          return False
                    
         
    def search(self, searchWord: str) -> bool:
          # dfs搜索Trie树中的所有单词
          return self.dfs(searchWord, 0, 0, 0)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的 C 语言字典 Trie 的代码实现: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_WORD_LEN 100 typedef struct TrieNode { char value; struct TrieNode *children[26]; int is_end; } TrieNode; TrieNode *createNode(char value) { TrieNode *node = (TrieNode *) malloc(sizeof(TrieNode)); node->value = value; for (int i = 0; i < 26; i++) { node->children[i] = NULL; } node->is_end = 0; return node; } void insert(TrieNode *root, char *word) { TrieNode *curr = root; int len = strlen(word); for (int i = 0; i < len; i++) { int index = word[i] - 'a'; if (curr->children[index] == NULL) { curr->children[index] = createNode(word[i]); } curr = curr->children[index]; } curr->is_end = 1; } int search(TrieNode *root, char *word) { TrieNode *curr = root; int len = strlen(word); for (int i = 0; i < len; i++) { int index = word[i] - 'a'; if (curr->children[index] == NULL) { return 0; } curr = curr->children[index]; } return curr->is_end; } int main() { TrieNode *root = createNode('\0'); char word[MAX_WORD_LEN]; int choice = 0; do { printf("1. Insert Word\n"); printf("2. Search Word\n"); printf("3. Exit\n"); printf("Enter Choice: "); scanf("%d", &choice); switch (choice) { case 1: printf("Enter Word to Insert: "); scanf("%s", word); insert(root, word); break; case 2: printf("Enter Word to Search: "); scanf("%s", word); if (search(root, word)) { printf("%s is present in the dictionary.\n", word); } else { printf("%s is not present in the dictionary.\n", word); } break; case 3: printf("Exiting...\n"); break; default: printf("Invalid Choice!\n"); break; } } while (choice != 3); return 0; } ``` 该实现中使用了一个 TrieNode 结构体来表示 Trie 中的每个节点,其中包含了节点的值,子节点指针数组和一个标志位,用于指示该节点是否为单词的结尾。 在插入单词时,从根节点开始遍历 Trie ,如果当前节点的相应子节点为空,则新建一个节点并将其作为当前节点的相应子节点。最后将单词的结尾节点的标志位设置为 1。 在查找单词时,同样从根节点开始遍历 Trie ,如果当前节点的相应子节点为空,则说明该单词不存在于 Trie 中。如果单词的最后一个字符所在的节点的标志位为 1,则说明该单词存在于 Trie 中。 该实现中还包含了一个简单的命令行界面,用于接收用户的输入并执行相应的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值