LeetCode第211题_添加与搜索单词

LeetCode 第211题:添加与搜索单词 - 数据结构设计

题目描述

请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。

实现词典类 WordDictionary

  • WordDictionary() 初始化词典对象
  • void addWord(word)word 添加到数据结构中,之后可以对它进行匹配
  • bool search(word) 如果数据结构中存在字符串与 word 匹配,则返回 true;否则,返回 falseword 中可能包含一些 .,每个 . 都可以表示任何一个字母。

难度

中等

题目链接

点击在LeetCode中查看题目

示例

输入:
["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
  • word 仅由小写英文字母和 . 组成
  • 最多调用 3 * 10^4addWordsearch

解题思路

这道题是要实现一个支持通配符查找的单词字典,主要有两个操作:添加单词和搜索单词。搜索时可能包含通配符.,代表任意一个字符。

由于涉及到字符串的存储和前缀匹配,我们可以使用**Trie(前缀树)**这种数据结构来解决问题。Trie树特别适合用来解决需要快速检索字符串的问题。

方法:Trie树 + 深度优先搜索

我们可以构建一个Trie树来存储所有添加的单词,而当搜索包含通配符的单词时,使用深度优先搜索(DFS)来探索所有可能的匹配路径。

关键点:

  1. 设计Trie节点结构,包含子节点和是否是单词结尾的标记
  2. 实现addWord方法,将单词添加到Trie树中
  3. 实现search方法,结合DFS来处理通配符的情况

当遇到通配符.时,需要尝试所有可能的字符,这可以通过递归搜索Trie树的所有子节点来实现。

时间复杂度:

  • addWord: O(m),其中m是单词的长度
  • search:
    • 最好情况(无通配符):O(m)
    • 最坏情况(全部是通配符):O(26^m),因为每个位置都有26种可能

空间复杂度:O(N*m),其中N是单词的数量,m是平均单词长度

代码实现

C# 实现

public class WordDictionary {
    private class TrieNode {
        public TrieNode[] Children { get; }
        public bool IsEndOfWord { get; set; }
        
        public TrieNode() {
            Children = new TrieNode[26]; // 26个小写字母
            IsEndOfWord = false;
        }
    }
    
    private readonly TrieNode root;

    public WordDictionary() {
        root = new TrieNode();
    }
    
    public void AddWord(string word) {
        TrieNode node = root;
        
        foreach (char c in word) {
            int index = c - 'a';
            if (node.Children[index] == null) {
                node.Children[index] = new TrieNode();
            }
            node = node.Children[index];
        }
        
        node.IsEndOfWord = true;
    }
    
    public bool Search(string word) {
        return SearchHelper(word, 0, root);
    }
    
    private bool SearchHelper(string word, int index, TrieNode node) {
        // 已经处理完整个单词
        if (index == word.Length) {
            return node.IsEndOfWord;
        }
        
        char c = word[index];
        
        // 如果是通配符,则尝试所有可能的字符
        if (c == '.') {
            for (int i = 0; i < 26; i++) {
                if (node.Children[i] != null && SearchHelper(word, index + 1, node.Children[i])) {
                    return true;
                }
            }
            return false;
        } 
        // 普通字符,按照常规Trie查找
        else {
            int charIndex = c - 'a';
            return node.Children[charIndex] != null && 
                   SearchHelper(word, index + 1, node.Children[charIndex]);
        }
    }
}

/**
 * Your WordDictionary object will be instantiated and called as such:
 * WordDictionary obj = new WordDictionary();
 * obj.AddWord(word);
 * bool param_2 = obj.Search(word);
 */

Python 实现

class WordDictionary:
    def __init__(self):
        self.trie = {}  # 使用字典实现Trie

    def addWord(self, word: str) -> None:
        node = self.trie
        for char in word:
            if char not in node:
                node[char] = {}
            node = node[char]
        node['$'] = True  # 使用'$'标记单词结尾

    def search(self, word: str) -> bool:
        def dfs(node, index):
            # 如果已经处理完整个单词
            if index == len(word):
                return '$' in node
            
            char = word[index]
            
            # 如果是通配符,则尝试所有可能的字符
            if char == '.':
                for key in node:
                    if key != '$' and dfs(node[key], index + 1):
                        return True
                return False
            # 普通字符,按照常规Trie查找
            else:
                if char not in node:
                    return False
                return dfs(node[char], index + 1)
        
        return dfs(self.trie, 0)

# Your WordDictionary object will be instantiated and called as such:
# obj = WordDictionary()
# obj.addWord(word)
# param_2 = obj.search(word)

C++ 实现

class WordDictionary {
private:
    struct TrieNode {
        TrieNode* children[26];
        bool isEndOfWord;
        
        TrieNode() {
            for (int i = 0; i < 26; i++) {
                children[i] = nullptr;
            }
            isEndOfWord = false;
        }
        
        ~TrieNode() {
            for (int i = 0; i < 26; i++) {
                if (children[i]) {
                    delete children[i];
                }
            }
        }
    };
    
    TrieNode* root;
    
    bool searchHelper(const string& word, int index, TrieNode* node) {
        // 已经处理完整个单词
        if (index == word.length()) {
            return node->isEndOfWord;
        }
        
        char c = word[index];
        
        // 如果是通配符,则尝试所有可能的字符
        if (c == '.') {
            for (int i = 0; i < 26; i++) {
                if (node->children[i] && searchHelper(word, index + 1, node->children[i])) {
                    return true;
                }
            }
            return false;
        } 
        // 普通字符,按照常规Trie查找
        else {
            int charIndex = c - 'a';
            return node->children[charIndex] && 
                   searchHelper(word, index + 1, node->children[charIndex]);
        }
    }
    
public:
    WordDictionary() {
        root = new TrieNode();
    }
    
    ~WordDictionary() {
        delete root;
    }
    
    void addWord(string word) {
        TrieNode* node = root;
        
        for (char c : word) {
            int index = c - 'a';
            if (!node->children[index]) {
                node->children[index] = new TrieNode();
            }
            node = node->children[index];
        }
        
        node->isEndOfWord = true;
    }
    
    bool search(string word) {
        return searchHelper(word, 0, root);
    }
};

/**
 * Your WordDictionary object will be instantiated and called as such:
 * WordDictionary* obj = new WordDictionary();
 * obj->addWord(word);
 * bool param_2 = obj->search(word);
 */

性能分析

各语言实现的性能对比:

实现语言执行用时内存消耗特点
C#400 ms76.2 MB使用数组表示子节点,DFS递归搜索
Python288 ms27.3 MB使用字典实现更灵活,代码简洁
C++88 ms45.1 MB性能最佳,直接内存管理

补充说明

代码亮点

  1. 使用Trie树数据结构存储单词,提高检索效率
  2. 针对通配符的情况,采用DFS递归搜索所有可能的分支
  3. 递归函数设计简洁清晰,易于理解
  4. C++实现中注意了内存管理,防止内存泄漏

Trie树与通配符搜索结合

本题的难点在于如何处理通配符,我们采用递归DFS的方式,当遇到通配符时,尝试当前节点的所有子节点。这种方法可能在最坏情况下(如搜索"...")会导致大量的搜索分支,但在实际应用中,通配符通常不会太多,因此性能通常是可接受的。

优化方向

  1. 如果通配符特别多,可以考虑使用记忆化搜索来避免重复计算
  2. 对于特殊情况,如全部是通配符的查询,可以提前计算字典中各长度单词的数量,直接返回结果
  3. 可以根据实际应用场景调整Trie节点的实现,比如使用哈希表代替数组存储子节点,以支持更大的字符集

常见错误

  1. 忘记标记单词结尾,导致无法判断完整单词
  2. 递归终止条件设置不当,导致越界或死循环
  3. 处理通配符时遗漏某些分支
  4. C++实现中没有正确管理内存,导致内存泄漏

相关题目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值