LeetCode 208. Implement Trie (Prefix Tree)

题目

mplement a trie with insertsearch, 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);
 */

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值