Welcome to Subscribe On Youtube

Question

Formatted question description: https://leetcode.ca/all/208.html

A trie (pronounced as "try") or prefix tree is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker.

Implement the Trie class:

  • Trie() Initializes the trie object.
  • void insert(String word) Inserts the string word into the trie.
  • boolean search(String word) Returns true if the string word is in the trie (i.e., was inserted before), and false otherwise.
  • boolean startsWith(String prefix) Returns true if there is a previously inserted string word that has the prefix prefix, and false otherwise.

 

Example 1:

Input
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
Output
[null, null, true, false, true, null, true]

Explanation
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple");   // return True
trie.search("app");     // return False
trie.startsWith("app"); // return True
trie.insert("app");
trie.search("app");     // return True

 

Constraints:

  • 1 <= word.length, prefix.length <= 2000
  • word and prefix consist only of lowercase English letters.
  • At most 3 * 104 calls in total will be made to insert, search, and startsWith.

Algorithm

Using a basic Trie (pronounced as “try”), which is a type of search tree used to efficiently store a dynamic set or associative array where the keys are usually strings. Tries are well-suited for solving problems related to word searches, auto-completions, and prefix matching.

TrieNode Class:

  • Purpose: Represents a single node in the Trie.
  • child Attribute: Uses a defaultdict from the collections module to automatically create a new TrieNode when accessing a missing key. This eliminates the need for explicit checks if a child node exists, streamlining node creation during word insertion.
  • is_word Attribute: A boolean flag indicating whether the node marks the end of a word in the Trie. It is initialized as False and set to True when a word is fully inserted.

Trie Class:

  • Initialization: The Trie constructor initializes the Trie with a root TrieNode.

  • insert Method: Inserts a word into the Trie.
    • Iterates through each letter in the word.
    • For each letter, it moves down to the corresponding child node, creating new nodes as necessary due to the use of defaultdict.
    • After all letters are inserted, sets the is_word flag of the last node to True to mark the end of a word.
  • search Method: Searches for a word in the Trie.
    • Iterates through each letter in the word, navigating through the child nodes.
    • If a letter does not exist as a key in the current node’s children (i.e., cur.child.get(letter) returns None), returns False immediately.
    • If all letters are found in sequence, checks the is_word flag of the last node. Returns True if it’s set (meaning the word exists in the Trie), otherwise False.
  • startsWith Method: Checks if there is any word in the Trie that starts with the given prefix.
    • Similar to search, but returns True as soon as all letters of the prefix are found in sequence, without checking the is_word flag. This is because the presence of the prefix nodes alone is sufficient to confirm that at least one word with that prefix exists in the Trie.

Usage and Functionality:

This Trie implementation allows for efficient insertion and search operations for words and prefixes. The defaultdict significantly simplifies the code by automatically handling missing children. This Trie can be used in various applications such as implementing an autocomplete feature, spell checker, or for efficiently solving word search puzzles.

Note: The comment in the search and startsWith methods about cur = cur.child[letter] potentially creating a default node is crucial. Using cur.child.get(letter) instead avoids unintended Trie modifications during search operations, ensuring that the Trie structure only changes during explicit insertions.

Code

  • 
    public class Implement_Trie_Prefix_Tree {
    
        public static void main(String[] args) {
            Implement_Trie_Prefix_Tree out = new Implement_Trie_Prefix_Tree();
    
            Trie trie = out.new Trie();
            trie.insert("abc");
            trie.insert("ab");
            System.out.println(trie.search("ab"));
            trie.insert("ab");
            System.out.println(trie.search("ab"));
            System.out.println(trie.startsWith("a"));
    
        }
    
        class TrieNode {
    
            // R children to node children
            private TrieNode[] children;
    
            private final int R = 26;
    
            private boolean isEnd;
    
            public TrieNode() {
                children = new TrieNode[R];
            }
    
            public boolean containsKey(char ch) {
                return children[ch - 'a'] != null;
            }
    
            public TrieNode get(char ch) {
                return children[ch - 'a'];
            }
    
            public void put(char ch, TrieNode node) {
                children[ch - 'a'] = node;
            }
    
            public void setEnd() {
                isEnd = true;
            }
    
            public boolean isEnd() {
                return isEnd;
            }
        }
    
        class Trie {
            private TrieNode root;
    
            public Trie() {
                root = new TrieNode();
            }
    
            // Inserts a word into the trie.
            public void insert(String word) {
                TrieNode node = root;
    
                // @note:@memorize: iteration is better than recursion during trie building
                for (int i = 0; i < word.length(); i++) {
                    char currentChar = word.charAt(i);
                    if (!node.containsKey(currentChar)) {
                        node.put(currentChar, new TrieNode());
                    }
                    node = node.get(currentChar);
                }
                node.setEnd();
            }
    
            // search a prefix or whole key in trie and
            // returns the node where search ends
            private TrieNode searchPrefix(String word) {
                TrieNode node = root;
                for (int i = 0; i < word.length(); i++) {
                    char curLetter = word.charAt(i);
                    if (node.containsKey(curLetter)) {
                        node = node.get(curLetter);
                    } else {
                        return null;
                    }
                }
                return node;
            }
    
            // Returns if the word is in the trie.
            public boolean search(String word) {
                TrieNode node = searchPrefix(word);
                return node != null && node.isEnd();
            }
    
            // Returns if there is any word in the trie
            // that starts with the given prefix.
            public boolean startsWith(String prefix) {
                TrieNode node = searchPrefix(prefix);
                return node != null;
            }
        }
    
        // Your Trie object will be instantiated and called as such:
        // Trie trie = new Trie();
        // trie.insert("somestring");
        // trie.search("key");
    
    }
    
    
    //////
    
    class Trie {
        private Trie[] children;
        private boolean isEnd;
    
        public Trie() {
            children = new Trie[26];
        }
    
        public void insert(String word) {
            Trie node = this;
            for (char c : word.toCharArray()) {
                int idx = c - 'a';
                if (node.children[idx] == null) {
                    node.children[idx] = new Trie();
                }
                node = node.children[idx];
            }
            node.isEnd = true;
        }
    
        public boolean search(String word) {
            Trie node = searchPrefix(word);
            return node != null && node.isEnd;
        }
    
        public boolean startsWith(String prefix) {
            Trie node = searchPrefix(prefix);
            return node != null;
        }
    
        private Trie searchPrefix(String s) {
            Trie node = this;
            for (char c : s.toCharArray()) {
                int idx = c - 'a';
                if (node.children[idx] == null) {
                    return null;
                }
                node = node.children[idx];
            }
            return node;
        }
    }
    
    /**
     * 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);
     */
    
  • // OJ: https://leetcode.com/problems/implement-trie-prefix-tree/
    // Time: O(W) for insert/search/startsWith
    // Space: O(1) extra space for all
    struct TrieNode {
        TrieNode *next[26] = {};
        bool word = false;
    };
    class Trie {
        TrieNode root;
        TrieNode *find(string &word) {
            auto node = &root;
            for (char c : word) {
                if (!node->next[c - 'a']) return NULL;
                node = node->next[c - 'a'];
            }
            return node;
        }
    public:
        void insert(string word) {
            auto node = &root;
            for (char c : word) {
                if (!node->next[c - 'a']) node->next[c - 'a'] = new TrieNode();
                node = node->next[c - 'a'];
            }
            node->word = true;
        }
        bool search(string word) {
            auto node = find(word);
            return node && node->word;
        }
        bool startsWith(string prefix) {
            return find(prefix);
        }
    };
    
  • 
    import collections
    
    class TrieNode:
    
    	def __init__(self):
        '''
        Usually, a Python dictionary throws a KeyError if you try to get an item with a key that is not currently in the dictionary.
        The defaultdict in contrast will simply create any items that you try to access
    
        https://stackoverflow.com/questions/5900578/how-does-collections-defaultdict-work
        '''
    		self.child = collections.defaultdict(TrieNode)
    		self.is_word = False
    
    class Trie:
    
    	def __init__(self):
    		self.root = TrieNode()
    
    	def insert(self, word: str) -> None:
    		cur = self.root
    		for letter in word:
    			cur = cur.child[letter]
    		cur.is_word = True
    
    	def search(self, word: str) -> bool:
    		cur = self.root
    		for letter in word:
    			# cur = cur.child[letter] ==> will not work, it will creat a default node for this letter
    			cur = cur.child.get(letter)
    			if not cur:
    				return False
    		return cur.is_word
    
    	def startsWith(self, prefix: str) -> bool:
    		cur = self.root
    		for letter in prefix:
    			# cur = cur.child[letter] ==> will not work, it will creat a default node for this letter
    			cur = cur.child.get(letter)
    			if not cur:
    				return False
    		return True
    
    # Your Trie object will be instantiated and called as such:
    # obj = Trie()
    # obj.insert(word)
    # param_2 = obj.search(word)
    # param_3 = obj.startsWith(prefix)
    
    
    ############
    
    class Trie:
        def __init__(self):
            self.children = [None] * 26
            self.is_end = False
    
        def insert(self, word: str) -> None:
            node = self
            for c in word:
                idx = ord(c) - ord('a')
                if node.children[idx] is None:
                    node.children[idx] = Trie()
                node = node.children[idx]
            node.is_end = True
    
        def search(self, word: str) -> bool:
            node = self._search_prefix(word)
            return node is not None and node.is_end
    
        def startsWith(self, prefix: str) -> bool:
            node = self._search_prefix(prefix)
            return node is not None
    
        def _search_prefix(self, prefix: str):
            node = self
            for c in prefix:
                idx = ord(c) - ord('a')
                if node.children[idx] is None:
                    return None
                node = node.children[idx]
            return node
    
    
    # Your Trie object will be instantiated and called as such:
    # obj = Trie()
    # obj.insert(word)
    # param_2 = obj.search(word)
    # param_3 = obj.startsWith(prefix)
    
    
    
    # below is using reduce()
    class Trie(object):
    
        def __init__(self):
            T = lambda: collections.defaultdict(T)
            self.root = T()
    
        def insert(self, word):
            reduce(dict.__getitem__, word, self.root)['#'] = True
    
        def search(self, word):
            return '#' in reduce(lambda cur, c: cur.get(c, {}), word, self.root)
    
        def startsWith(self, prefix):
            return bool(reduce(lambda cur, c: cur.get(c, {}), prefix, self.root))
    
    
  • type Trie struct {
    	children [26]*Trie
    	isEnd    bool
    }
    
    func Constructor() Trie {
    	return Trie{}
    }
    
    func (this *Trie) Insert(word string) {
    	node := this
    	for _, c := range word {
    		idx := c - 'a'
    		if node.children[idx] == nil {
    			node.children[idx] = &Trie{}
    		}
    		node = node.children[idx]
    	}
    	node.isEnd = true
    }
    
    func (this *Trie) Search(word string) bool {
    	node := this.SearchPrefix(word)
    	return node != nil && node.isEnd
    }
    
    func (this *Trie) StartsWith(prefix string) bool {
    	node := this.SearchPrefix(prefix)
    	return node != nil
    }
    
    func (this *Trie) SearchPrefix(s string) *Trie {
    	node := this
    	for _, c := range s {
    		idx := c - 'a'
    		if node.children[idx] == nil {
    			return nil
    		}
    		node = node.children[idx]
    	}
    	return node
    }
    
    /**
     * Your Trie object will be instantiated and called as such:
     * obj := Constructor();
     * obj.Insert(word);
     * param_2 := obj.Search(word);
     * param_3 := obj.StartsWith(prefix);
     */
    
  • class TrieNode {
        children;
        isEnd;
        constructor() {
            this.children = new Array(26);
            this.isEnd = false;
        }
    }
    
    class Trie {
        root;
        constructor() {
            this.root = new TrieNode();
        }
    
        insert(word: string): void {
            let head = this.root;
            for (let char of word) {
                let index = char.charCodeAt(0) - 97;
                if (!head.children[index]) {
                    head.children[index] = new TrieNode();
                }
                head = head.children[index];
            }
            head.isEnd = true;
        }
    
        search(word: string): boolean {
            let head = this.searchPrefix(word);
            return head != null && head.isEnd;
        }
    
        startsWith(prefix: string): boolean {
            return this.searchPrefix(prefix) != null;
        }
    
        private searchPrefix(prefix: string) {
            let head = this.root;
            for (let char of prefix) {
                let index = char.charCodeAt(0) - 97;
                if (!head.children[index]) return null;
                head = head.children[index];
            }
            return head;
        }
    }
    
    
  • /**
     * Initialize your data structure here.
     */
    var Trie = function () {
        this.children = {};
    };
    
    /**
     * Inserts a word into the trie.
     * @param {string} word
     * @return {void}
     */
    Trie.prototype.insert = function (word) {
        let node = this.children;
        for (let char of word) {
            if (!node[char]) {
                node[char] = {};
            }
            node = node[char];
        }
        node.isEnd = true;
    };
    
    /**
     * Returns if the word is in the trie.
     * @param {string} word
     * @return {boolean}
     */
    Trie.prototype.search = function (word) {
        let node = this.searchPrefix(word);
        return node != undefined && node.isEnd != undefined;
    };
    
    Trie.prototype.searchPrefix = function (prefix) {
        let node = this.children;
        for (let char of prefix) {
            if (!node[char]) return false;
            node = node[char];
        }
        return node;
    };
    
    /**
     * Returns if there is any word in the trie that starts with the given prefix.
     * @param {string} prefix
     * @return {boolean}
     */
    Trie.prototype.startsWith = function (prefix) {
        return this.searchPrefix(prefix);
    };
    
    /**
     * Your Trie object will be instantiated and called as such:
     * var obj = new Trie()
     * obj.insert(word)
     * var param_2 = obj.search(word)
     * var param_3 = obj.startsWith(prefix)
     */
    
    
  • public class Trie {
        bool isEnd;
        Trie[] children = new Trie[26];
    
        public Trie() {
    
        }
    
        public void Insert(string word) {
            Trie node = this;
            foreach (var c in word)
            {
                var idx = c - 'a';
                node.children[idx] ??= new Trie();
                node = node.children[idx];
            }
            node.isEnd = true;
        }
    
        public bool Search(string word) {
            Trie node = SearchPrefix(word);
            return node != null && node.isEnd;
        }
    
        public bool StartsWith(string prefix) {
            Trie node = SearchPrefix(prefix);
            return node != null;
        }
    
        private Trie SearchPrefix(string s) {
            Trie node = this;
            foreach (var c in s)
            {
                var idx = c - 'a';
                if (node.children[idx] == null)
                {
                    return null;
                }
                node = node.children[idx];
            }
            return node;
        }
    }
    
    /**
     * 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);
     */
    
  • use std::{rc::Rc, cell::RefCell, collections::HashMap};
    
    struct TrieNode {
        pub val: Option<char>,
        pub flag: bool,
        pub child: HashMap<char, Rc<RefCell<TrieNode>>>,
    }
    
    impl TrieNode {
        fn new() -> Self {
            Self {
                val: None,
                flag: false,
                child: HashMap::new(),
            }
        }
    
        fn new_with_val(val: char) -> Self {
            Self {
                val: Some(val),
                flag: false,
                child: HashMap::new(),
            }
        }
    }
    
    struct Trie {
        root: Rc<RefCell<TrieNode>>,
    }
    
    /// Your Trie object will be instantiated and called as such:
    /// let obj = Trie::new();
    /// obj.insert(word);
    /// let ret_2: bool = obj.search(word);
    /// let ret_3: bool = obj.starts_with(prefix);
    impl Trie {
        fn new() -> Self {
            Self {
                root: Rc::new(RefCell::new(TrieNode::new())),
            }
        }
    
        fn insert(&self, word: String) {
            let char_vec: Vec<char> = word.chars().collect();
            // Get the clone of current root node
            let mut root = Rc::clone(&self.root);
            for c in &char_vec {
                if !root.borrow().child.contains_key(c) {
                    // We need to manually create the entry
                    root.borrow_mut().child.insert(*c, Rc::new(RefCell::new(TrieNode::new())));
                }
                // Get the child node
                let root_clone = Rc::clone(root.borrow().child.get(c).unwrap());
                root = root_clone;
            }
            {
                root.borrow_mut().flag = true;
            }
        }
    
        fn search(&self, word: String) -> bool {
            let char_vec: Vec<char> = word.chars().collect();
            // Get the clone of current root node
            let mut root = Rc::clone(&self.root);
            for c in &char_vec {
                if !root.borrow().child.contains_key(c) {
                    return false;
                }
                // Get the child node
                let root_clone = Rc::clone(root.borrow().child.get(c).unwrap());
                root = root_clone;
            }
            let flag = root.borrow().flag;
            flag
        }
    
        fn starts_with(&self, prefix: String) -> bool {
            let char_vec: Vec<char> = prefix.chars().collect();
            // Get the clone of current root node
            let mut root = Rc::clone(&self.root);
            for c in &char_vec {
                if !root.borrow().child.contains_key(c) {
                    return false;
                }
                // Get the child node
                let root_clone = Rc::clone(root.borrow().child.get(c).unwrap());
                root = root_clone;
            }
            true
        }
    }
    

All Problems

All Solutions