题目:
mplement a trie with insert
, search
, and startsWith
methods.
Example:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // returns true
trie.search("app"); // returns false
trie.startsWith("app"); // returns true
trie.insert("app");
trie.search("app"); // returns true
Note:
- You may assume that all inputs are consist of lowercase letters
a-z
. - All inputs are guaranteed to be non-empty strings.
2020.10.5
面试面到了这道题emmmm 之前没复习,有点凉。自己从头开始想的时候只能想到node的children是一个list,但是忽略了要判断一个char是不是在一个list里还需要O(n)的查找时间,然后就说那我们用set吧,说完set写到后面发现我要get到某个char对应的node的时候就感觉需要一个char到node的mapping又改说hashmap……啊,就没有想到可以直接用一个长度为26的数组,很卑微。而且当时还想着每个node要有一个它对应的char value……啊反正想的乱七八糟的唉,真的直接用数组就完事儿了,一定要记住!
面试的时候主要是说user input is stream,每次一个char输入就要判断它是不是一个valid word。于是就还在Trie里加了个curr表示当前所在的层数。另外如果press backspace的话,可以通过加一个从child到parent的pointer来往回走一层。
回到lc这道题上,知道了可以用数组不需要value以后写代码就方便多了,然后也加上了Node class的一些helper method来判断。现在回头看面试写的真是不堪入目。lc写的算是半个一遍bug free,有些编译的小问题,以及数组下标忘了- 'a',改完就一遍过了。
Runtime: 30 ms, faster than 88.76% of Java online submissions for Implement Trie (Prefix Tree).
Memory Usage: 50.1 MB, less than 55.77% of Java online submissions for Implement Trie (Prefix Tree).
class Trie {
class Node {
private Node[] children;
private final int NUM = 26;
private boolean isEnd;
public Node() {
children = new Node[NUM];
isEnd = false;
}
public boolean containsChild(char child) {
if (children[child - 'a'] == null) {
return false;
}
return true;
}
public Node getChild(char child) {
return children[child - 'a'];
}
public void setChild(char child) {
children[child - 'a'] = new Node();
}
public void setEnd() {
isEnd = true;
}
public boolean isEnd() {
return isEnd;
}
}
private Node root;
/** Initialize your data structure here. */
public Trie() {
root = new Node();
}
/** Inserts a word into the trie. */
public void insert(String word) {
Node curr = root;
for (char c : word.toCharArray()) {
if (!curr.containsChild(c)) {
curr.setChild(c);
}
curr = curr.getChild(c);
}
curr.setEnd();
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
Node curr = root;
for (char c : word.toCharArray()) {
if (!curr.containsChild(c)) {
return false;
}
curr = curr.getChild(c);
}
return curr.isEnd();
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
Node curr = root;
for (char c : prefix.toCharArray()) {
if (!curr.containsChild(c)) {
return false;
}
curr = curr.getChild(c);
}
return true;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
曾经的C++笔记:
这道题是要实现一个Trie,Trie是一种多叉树,除了根节点外,每个节点表示一个字符,把一条路径上的所有字符相连可以组成一个单词,通常用于字符串查询、字符串排序、前缀匹配、词频统计等。用来表示一个Trie节点的数据结构应该包括:数组children表示一个节点的所有子节点,bool is_end表示以这个节点结束的字符串是否为一个单词。
对于Trie的插入,我们只需要遍历一遍待加入的字符串,将字符串中的每个字母依次插入到当前节点的children中,当插入到最后一个字母时,把这个节点标记为is_end;查找一个字符串时,也是遍历一遍待查找的字符串,查看当前节点的children中是否含有当前遍历到的字母,如果没有则不存在,且查找到最后一个字母时,需要查看这个节点是否被标记为is_end。如果要查找前缀,和查找字符串几乎相同,只是不需要再查看最后一个字母是否被标记为is_end即可。
这道题主要的收获在于熟悉了类的写法。刚开始真的完全不知道怎么去设计这个Trie,想着到底要不要再开一个类叫做TrieNode,然后Trie里面先声明一个TrieNode为root然后再对这个root进行操作,以及是否需要把当前节点的字符存成一个val field。后来看了discussion里面一位大佬的解法直接使用了Trie,真的非常简洁优雅,就学习了大佬的写法。首先是Trie里面用来存children的,刚开始自己思考的时候在想是不是要用一个vector,看了大佬的代码发现其实用普通的长度为26的数组也可以,还更方便一些;然后是Trie的构造函数,居然可以什么都不用写,可能是因为在声明成员变量的时候已经初始化了?也可以在声明成员变量的时候先不初始化,然后在构造函数中初始化(见注释)。自己在实现的时候也遇到了不少坑,比如this和node傻傻分不清,以后一定要多注意。
代码如下,72ms,79.06%,44.8M,56.67%:
class Trie {
public:
/** Initialize your data structure here. */
Trie* children[26] = {};
bool is_end = false;
Trie() {
}
/*Trie* children[26];
bool is_end;
Trie() {
memset(children, 0, sizeof(children));
is_end = false;
}*/
/** Inserts a word into the trie. */
void insert(string word) {
Trie* node = this;
for (int i = 0; i < word.size(); i++) {
int index = word[i] - 'a';
if (node->children[index] == NULL) {
node->children[index] = new Trie();
}
node = node->children[index];
}
node->is_end = true;
}
/** Returns if the word is in the trie. */
bool search(string word) {
Trie* node = this;
for (int i = 0; i < word.size(); i++) {
int index = word[i] - 'a';
if (node->children[index] == NULL) {
return false;
}
/*if (i == word.size() - 1 && node->is_end) {
return true;
}*/ // same
node = node->children[index];
}
return node->is_end;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
Trie* node = this;
for (int i = 0; i < prefix.size(); i++) {
int index = prefix[i] - 'a';
if (node->children[index] == NULL) {
return false;
}
node = node->children[index];
}
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/
另外也写了一个单独写了TrieNode class的版本,这个就是要在Trie的成员变量中声明一个TrieNode* root,然后初始化的时候new一个TrieNode出来。这种版本68ms,88.56%,44.7M,56.67%:
class TrieNode {
public:
TrieNode* children[26] = {};
bool is_end = false;
};
class Trie {
public:
/** Initialize your data structure here. */
TrieNode* root;
Trie() {
root = new TrieNode();
}
/** Inserts a word into the trie. */
void insert(string word) {
TrieNode* node = root;
for (int i = 0; i < word.size(); i++) {
int index = word[i] - 'a';
if (node->children[index] == NULL) {
node->children[index] = new TrieNode();
}
node = node->children[index];
}
node->is_end = true;
}
/** Returns if the word is in the trie. */
bool search(string word) {
TrieNode* node = root;
for (int i = 0; i < word.size(); i++) {
int index = word[i] - 'a';
if (node->children[index] == NULL) {
return false;
}
if (i == word.size() - 1 && node->is_end) {
return true;
}
node = node->children[index];
}
return node->is_end;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
TrieNode* node = root;
for (int i = 0; i < prefix.size(); i++) {
int index = prefix[i] - 'a';
if (node->children[index] == NULL) {
return false;
}
node = node->children[index];
}
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/