写点什么

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

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

系列文章:

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

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 示例

输入:
["WordDictionary","addWord","addWord","addWord","search","search","search","search"]
[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]
输出:
[null,null,null,null,false,true,true,true]
复制代码

2.3 基础知识——Trie 树

2.3.1 概念

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

2.3.2 基本特性

  • 根节点不包含字符,除根节点外每一个节点都只包含一个字符;

  • 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;

  • 每个节点的所有子节点包含的字符都不相同。

2.3.3 基本操作

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

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

三 题目解析

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 树和字典两个类。

Trie 树:

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; }}
复制代码

WordDictionary:

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; }}
复制代码


发布于: 刚刚阅读数: 2
用户头像

磨炼中成长,痛苦中前行 2017.10.22 加入

微信公众号【程序员架构进阶】。多年项目实践,架构设计经验。曲折中向前,分享经验和教训

评论

发布
暂无评论
Leetcode 题目解析:211. 添加与搜索单词 - 数据结构设计