Leetcode 211: Add and Search Word - Data structure design
原文链接
https://blog.baozitraining.org/2019/03/leetcode-solution-211-add-and-search.html
Problem Statement
Design a data structure that supports the following two operations:
void addWord(word)
bool search(word)
search(word) can search a literal word or a regular expression string containing only letters a-z
or .
. A .
means it can represent any one letter.
Example:
addWord("bad")
addWord("dad")
addWord("mad")
search("pad") -> false
search("bad") -> true
search(".ad") -> true
search("b..") -> true
Problem link
Video Tutorial
You can find the detailed video tutorial here
https://youtu.be/qi2ohSEyyDw
Thought Process
It's a string pattern searching problem, and because it matches the word strictly from the beginning to the end (note, even the partial matching is considered False, e.g., word "abcd", search("abc") should return False), the Trie (prefix tree) data structure comes very handy. It's a common simple data structure that often comes in coding interviews, be sure you can write it bug free. Once we are familiar with Trie, addWord simply builds the Trie and search just looks for the exact matching. One tricky part of this problem is ".", which can match any one and only one letter. This requires a recursive search of all the sub-nodes in the trie when encountering a "." Even not asked in this leetcode question, but there are some very good follow up question
- If we support "*" matching, Similar to Leetcode wildcard matching, how would you solve it? Solution here
- How do you remove a word from the trie?
Solutions
class TrieNode {
public Character letter = null;
// I just like to indicate whether a node is end or not, being explicit and clear, also it's good to know
// if a word is actually a word or a substring of a long word
public boolean isEnd = false;
// This can be optimized by a 256 char array.
public Map<Character, TrieNode> children = new HashMap<>();
}
public class AddAndSearchWord {
private TrieNode root;
public AddAndSearchWord() {
this.root = new TrieNode();
}
// Adds a word into the data structure.
public void addWord(String word) {
if (word == null || word.length() == 0) {
return;
}
this.insertHelper(this.root, word, 0);
}
// this can be done iteratively too
private void insertHelper(TrieNode node, String word, int index) {
if (index == word.length()) {
node.isEnd = true;
return;
}
char c = word.charAt(index);
if (node.children.containsKey(c)) {
insertHelper(node.children.get(c), word, index + 1);
return;
}
TrieNode t = new TrieNode();
t.letter = c;
node.children.put(c, t);
insertHelper(t, word, index + 1);
}
// Returns if the word is in the trie. A word could
// contain the dot character '.' to represent any one and only one letter.
public boolean search(String word) {
if (word == null) {
return false;
}
return this.searchHelper(this.root, word, 0);
}
// DFS to search the word, worst case is to search the entire trie space if all dots
private boolean searchHelper(TrieNode node, String word, int index) {
if (node == null) {
return false;
}
// This is strict word matching, not including the substring, e.g., add("abcde"), search("abc") false, seasrch("abcde") true
if (index == word.length()) {
return node.isEnd;
}
char c = word.charAt(index);
if (c != '.') {
if (!node.children.containsKey(c)) {
return false;
}
return searchHelper(node.children.get(c), word, index + 1);
} else {
for (Map.Entry<Character, TrieNode> entry : node.children.entrySet()) {
// Don't need to go through a to z, just go through neighbours is enough, DFS
if (searchHelper(entry.getValue(), word, index + 1)) {
return true;
}
}
return false;
}
}
}
Time Complexity:
- addWord: O(N), N is the length of the word
- search: O(M), where M is the entire Trie's space (i.e., the total number of Trie nodes). Think about a worst case of N "............" dots. where N is the length of the word that is larger than the depth of the Trie (larger than the longest word seen so far). More specifically, it's N * (Nodes on Trie's each level) = N * (M / lgM) assuming trie's height is lgM, for worst case, N equals lgM, so N *(M / lgM) = lgM * (M / lgM) = M. Note a trie could be compressed (e.g., the single nodes are merged back to the upper level) but this analysis still holds true
Space Complexity:
- addWord: O(N), creating N more nodes in the trie, N is the length of the word
- search: No additional space needed
References
- GeeksForGeeks Trie
- Trie wildcard matching solution
- 利用Windows性能计数器(PerformanceCounter)监控
- zepto 基础知识(1)
- [C#7] 1.Tuples(元组)
- 防止“rm-rf/”误删除的5种方法
- 基于DotNetOpenAuth实现OpenID 服务提供者
- .NET 和Java 对象 XML序列化 库WOX
- jquery mobile 移动web(6)
- IIS6 间歇性的发生500错误的解决方法
- 产品之上的世界观
- 使用Windows 7中的库
- WordPress 开发之让浏览器自动加载最新的CSS、JS文件(免刷新缓存)
- [C#6] 0-概览
- 移植Windows自宿主WCF服务到Linux/Mono2.8
- IISWeb应用防火墙WAF
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android初学设置文字跑马灯效果
- 使用Zolom内存解析运行python脚本(不落地)
- 要点3:输入函数对比与自定义输入方式
- 性能测试必备命令(3)- lscpu
- 性能测试必备命令(2)- uptime
- Lua/luajit 点与冒号的区别
- vim的几种模式mode和按键映射map
- PHP parent 的注意点
- 不停服务调试(debug)线上Rsyslog
- 使用ulimit 命令、/etc/security/limits.conf、proc 调整系统参数
- 解决jupyter notebook matplotlib绘图中文乱码问题
- 【动手学深度学习笔记】之过拟合与欠拟合实例
- 【数学建模】之Matlab实现BP神经网络
- 【动手学深度学习笔记】之PyTorch实现多层感知机
- 【动手学深度学习笔记】之线性回归实现