前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Leetcode 题目解析:211. 添加与搜索单词 - 数据结构设计

Leetcode 题目解析:211. 添加与搜索单词 - 数据结构设计

作者头像
程序员架构进阶
发布2021-11-04 16:02:06
发布2021-11-04 16:02:06
61700
代码可运行
举报
文章被收录于专栏:架构进阶架构进阶
运行总次数:0
代码可运行

系列文章:

算法题目解析:从一道题目看动态规划

Leetcode 题目解析:274. H 指数

Leetcode 题目解析:279. 完全平方数

Leetcode 题目解析:287. 寻找重复数

一 摘要

在考察算法题时,我们往往离不开数据结构。而常见和常用的数据结构,以堆、栈、单/双链表、HashMap、各种二叉树(二叉树、平衡二叉树、搜索二叉树、红黑树)最为常见。另外,像bitmap等也比较多,尤其是需要位操作的时候。但还有一些数据结构也会占有一席之地,例如树中的Trie树(字典树),在检索类题目中也非常常见。

今天就以一道典型的字典树题目为例211. 添加与搜索单词 - 数据结构设计,再次熟悉一下这个数据结构。

二 题目描述与示例

2.1 描述

leetcode题目描述:

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

实现词典类 WordDictionary :

WordDictionary() 初始化词典对象

void addWord(word) 将 word 添加到数据结构中,之后可以对它进行匹配

bool search(word) 如果数据结构中存在字符串与 word 匹配,则返回 true ;否则,返回 false 。word 中可能包含一些 '.' ,每个 . 都可以表示任何一个字母。

2.2 示例

代码语言:javascript
代码运行次数:0
复制
输入:
代码语言:javascript
代码运行次数:0
复制
["WordDictionary","addWord","addWord","addWord","search","search","search","search"]

[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]

输出:

[null,null,null,null,false,true,true,true]
代码语言:javascript
代码运行次数:0
复制

2.3 基础知识——Trie树

2.3.1 概念

Trie树,又称单词查找树,是一种树形结构,哈希树的一种变种。Trie树的典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

2.3.2 基本特性

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

2.3.3 基本操作

Trie树的基本操作,有查找、插入和删除。在实际应用场景中,删除操作比较少见。

Trie树可以用O(∣S∣) 的时间复杂度完成向字典树插入元素 和 查询字符串是否在树中两个操作,其中 ∣S∣ 是插入字符串或查询前缀的长度:

2.3.4 Trie与哈希表的对比

  1. 最坏情况时间复杂度比hash表好
  2. 不会出现hash冲突,除非一个key对应多个值(除key外的其他信息)
  3. 自带排序功能(类似Radix Sort),中序遍历trie可以得到排序。

三 题目解析

3.1 描述解析

题目描述已经比较详细,这个就不用特别解释了。就是把输入的字符串逐个放到我们定义的WordDictionary结构中,并支持查找。这里实现3个方法,构造方法:WordDictionary(),添加方法 addWord(word),查找方法 search(word)。

3.2 示例解析

输入是两个数组,第一个数组是方法数组,按照顺序依次是构造,添加x3,查找x4;第二个数组是方法的参数,根据坐标一一对应。输出是上述8个方法的执行结果,构造方法和添加方法返回null,所以我们只要保证添加结果正确和查找判断是否存在方法准确,再封装成数组结构即可。

四 实现

4.1 关键问题

重点在于查找方法,对于搜索单词,从字典树的根结点开始搜索。由于待搜索的单词可能包含点号,因此在搜索过程中需要考虑点号的处理。对于当前字符是字母和点号的情况,分别按照如下方式处理:

  • 如果当前字符是字母,则判断当前字符对应的子结点是否存在,如果子结点存在则移动到子结点,继续搜索下一个字符,如果子结点不存在则说明单词不存在,返回false;
  • 如果当前字符是点.,由于点号可以表示任何字母,因此需要对当前结点的所有非空子结点继续搜索下一个字符。

重复上述步骤,直到返回false 或搜索完给定单词的最后一个字符。搜索完给定单词的最后一个字符,也就是搜索到的最后一个结点的isEnd标记为true时,判定给定的单词存在。特别情况:当搜索到点号时,只要存在一个非空子结点可以搜索到给定的单词,即返回true。

4.2 代码实现

还是采用Java代码实现,包括Trie树和字典两个类。

4.2.1 Trie树

Trie节点由children(trie数组)和isEnd标记两个元素组成,通过children构成树状结构,通过isEnd标记,标识单词到达结尾。

代码语言:javascript
代码运行次数:0
复制
public class Trie {
    private Trie[] children;
    private boolean isEnd;

    public Trie() {
        children = new Trie[26];
        isEnd = false;
    }

    public void insert(String word) {
        Trie node = this;
        for (int i = 0; i < word.length(); i++) {
            char ch = word.charAt(i);
            int index = ch - 'a';
            if (node.children[index] == null) {
                node.children[index] = new Trie();
            }
            node = node.children[index];
        }
        node.isEnd = true;
    }

    public Trie[] getChildren() {
        return children;
    }

    public boolean isEnd() {
        return isEnd;
    }
}
代码语言:javascript
代码运行次数:0
复制

4.2.2 WordDictionary

代码语言:javascript
代码运行次数:0
复制
package com.flamingstar.leetcode.tree;

public class WordDictionary {
    private Trie root;

    public WordDictionary() {
        root = new Trie();
    }

    public void addWord(String word) {
        root.insert(word);
    }

    public boolean search(String word) {
        return dfs(word, 0, root);
    }

    private boolean dfs(String word, int index, Trie node) {
        if (index == word.length()) {
            return node.isEnd();
        }
        char ch = word.charAt(index);
        if (Character.isLetter(ch)) {
            int childIndex = ch - 'a';
            Trie child = node.getChildren()[childIndex];
            if (child != null && dfs(word, index + 1, child)) {
                return true;
            }
        } else {
            for (int i = 0; i < 26; i++) {
                Trie child = node.getChildren()[i];
                if (child != null && dfs(word, index + 1, child)) {
                    return true;
                }
            }
        }
        return false;
    }
}
代码语言:javascript
代码运行次数:0
复制

4.3 复杂度分析

4.3.1 时间复杂度

初始化动作的时间复杂度为O(1),添加单词为O(∣S∣),搜索单词为 O(∣Σ∣|S∣),其中∣S∣ 是每次添加或搜索的单词的长度,Σ 是字符集,这道题中的字符集为全部小写英语字母,∣Σ∣=26。

最坏情况下,待搜索的单词中的每个字符都是点号,则每个字符都有∣Σ∣ 种可能。

4.3.2 空间复杂度

O(∣T∣⋅∣Σ∣),其中∣T∣ 是所有添加的单词的长度之和,Σ 是字符集,这道题中的字符集为全部小写英语字母,∣Σ∣=26。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员架构进阶 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一 摘要
  • 二 题目描述与示例
    • 2.1 描述
    • 2.2 示例
    • 2.3 基础知识——Trie树
      • 2.3.1 概念
      • 2.3.2 基本特性
      • 2.3.3 基本操作
      • 2.3.4 Trie与哈希表的对比
  • 三 题目解析
    • 3.1 描述解析
    • 3.2 示例解析
  • 四 实现
    • 4.1 关键问题
    • 4.2 代码实现
      • 4.2.1 Trie树
      • 4.2.2 WordDictionary
      • 4.3 复杂度分析
      • 4.3.1 时间复杂度
      • 4.3.2 空间复杂度
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档