- 📚 Blog home page: ⭐ This is a little Yibai blog duck~ ⭐ ️
- 👉 Welcome to pay attention ❤️ give the thumbs-up 👍 Collection ⭐ Comments 📝
- 😜 Xiaoyibai is preparing for his internship. He often updates the interview questions and LeetCode solutions. Friends with similar interests are welcome to communicate with each other~
- 💙 If you have any questions, please correct them. Remember to pay attention. Thank you~
Previous articles:
- Special summary of LeetCode sword finger Offer II linked list
- Special summary of LeetCode's Offer II hash table
- Special summary of LeetCode sword finger Offer II stack
- LeetCode refers to the special summary of Offer II queue
- Special summary of LeetCode sword finger Offer II tree (Part I)
- Special summary of LeetCode sword finger Offer II tree (Part 2)
- LeetCode sword refers to the special summary of Offer II stack
The first question is to understand the prefix tree. The application of the latter two questions is a little more difficult. The amount of code is very high, but the idea is very simple. I hope you don't discourage novices
062. Implement prefix tree
Title:
Trie (pronounced like "try") or prefix tree is a tree data structure used to efficiently store and retrieve keys in string data sets. This data structure has many application scenarios, such as automatic completion and spell checking.
Please implement the Trie class:
- Trie() initializes the prefix tree object.
- void insert(String word) inserts the string word into the prefix tree.
- boolean search(String word) returns true if the string word is in the prefix tree (that is, it has been inserted before retrieval); Otherwise, false is returned.
- boolean startsWith(String prefix) returns true if one of the prefixes of the previously inserted string word is prefix; Otherwise, false is returned.
Example:
input
inputs = ["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
inputs = [[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
output
[null, null, true, false, true, null, true]
explain
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
Tips:
- 1 <= word.length, prefix.length <= 2000
- word and prefix are composed of lowercase English letters only
- The total number of insert, search and startsWith calls shall not exceed 3 * 104
Idea:
Trie, also known as prefix tree or dictionary tree, is a rooted tree. Each node of trie contains the following fields:
- Pointer array children pointing to child nodes. For this question, the length of the array is 26, that is, the number of lowercase English letters. At this time, children[0] corresponds to lowercase letter a, children[1] corresponds to lowercase letter b,..., and children[25] corresponds to lowercase letter z.
- Boolean field isEnd, indicating whether the node is the end of the string.
I'm used to using next instead of children, because it's easier to write. This question is equivalent to introducing the prefix tree, so the code is also very simple. If you don't understand, you can baidu prefix tree first, understand it and then look at it. It's nothing more than replacing the binary tree with a 26 fork tree, and then a bool to judge whether it reaches the end, and then master the insertion and query methods.
class Trie { private: // Prefix tree, equivalent to 26 fork tree vector<Trie*> next; // Determine whether it is the end of the word bool isWrod; // Because both search and startsWith methods need to be queried, the query method is extracted // Directly return the last point of the query Trie* searchPrefix(string prefix) { Trie* node = this; for(char ch : prefix) { int index = ch - 'a'; if(node->next[index] != nullptr) { node = node->next[index]; }else { return nullptr; } } return node; } public: Trie():next(26),isWrod(false) {} void insert(string word) { int count = 0; // Points to the current class. Each class has 26 branches Trie* node = this; for(char ch : word) { int index = ch - 'a'; if(node->next[index] == nullptr) { // Create the index node of the parent node. At this time, index also has 26 branches node->next[index] = new Trie(); } // Point the parent class to the index node node = node->next[index]; } // After creating the current node, point to the last word and set it as the end of the word node->isWrod = true; } // If it is not empty and the isWrod of the last point is true, it means that word exists bool search(string word) { Trie* node = this->searchPrefix(word); return node != nullptr && node->isWrod; } // If it is not empty, the prefix is found bool startsWith(string prefix) { Trie* node = this->searchPrefix(prefix); return node != nullptr; } };
063. Replace words
Title:
In English, there is a concept called root, which can follow some other words to form another long word - we call this word success. For example, the root an, followed by the word other, can form a new word other.
Now, given a dictionary composed of many roots and a sentence, you need to replace all inherited words in the sentence with roots. If an inherited word has many roots that can form it, replace it with the shortest root.
You need to output the sentence after replacement.
Example:
Input: dictionary = ["cat", "bat", "rat"], sentence = "the cat was rated by the battery"
Output: "the cat was rat by the bat"
Tips:
- 1 <= dictionary.length <= 1000
- 1 <= dictionary[i].length <= 100
- dictionary[i] consists of lowercase letters only.
- 1 <= sentence.length <= 10^6
- sentence consists only of lowercase letters and spaces.
- The total number of words in sentence is in the range [1, 1000].
- The length of each word in sentence is in the range [1, 1000].
- Words in sentence are separated by a space.
- Sentince has no leading or trailing spaces.
Idea:
- Add the word root to the prefix tree, and the insert method is the same as the previous question
- Split the string and save the words in the array
- Processing strings is to intercept the root part of the word when the root is found, otherwise add the word directly
The prefixLen method is very similar to the query method of the following question. Originally, true was returned when the end was found, but now the root length is returned. If the end is not found, 0 is returned, which is convenient to judge whether the root is found
class Trie { private: bool isWord; vector<Trie*> next; public: Trie():next(26,nullptr),isWord(false){}; // Same as the previous question void insert(const string& word) { Trie* node = this; for(char ch : word) { int index = ch - 'a'; if(node->next[index] == nullptr) { node->next[index] = new Trie(); } node = node->next[index]; } node->isWord = true; } // It is slightly modified from the search in the previous question int prefixLen(const string& word) { Trie* node = this; // Returns the length of the prefix int len = 0; for(char ch : word) { int index = ch - 'a'; // There is no prefix if(node->next[index] == nullptr) { return 0; } node = node->next[index]; len++; // Find the prefix and return the length of the prefix if(node->isWord) return len; } return 0; } }; class Solution { public: string replaceWords(vector<string>& dictionary, string sentence) { Trie* root = new Trie(); // Add root to prefix tree for(auto& word : dictionary) { root->insert(word); } // Split string vector<string> words{""}; for(char ch : sentence) { if(ch == ' ') { words.push_back(""); }else { words.back().push_back(ch); } } // Process string string res; for(auto& word : words) { int len = root->prefixLen(word); // Direct add not found if(len == 0){ res += word + " "; }else { // Find the prefix and add only the prefix part res += word.substr(0, len) + " "; } } res.pop_back(); return res; } };
064. Magic dictionary
Title:
Design a data structure initialized with word list. The words in the word list are different from each other. If you give a word, decide whether you can replace only one letter of the word with another, so that the new word exists in the built magic dictionary.
Implement the MagicDictionary class:
MagicDictionary() initialization object
void buildDict(String[] dictionary) uses the string array dictionary to set the data structure. The strings in the dictionary are different from each other
bool search(String searchWord) gives a string searchWord to determine whether only one letter in the string can be replaced with another letter to form a string
Example:
input
inputs = ["MagicDictionary", "buildDict", "search", "search", "search", "search"]
inputs = [[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]]
output
[null, null, false, true, false, false]
explain
MagicDictionary magicDictionary = new MagicDictionary();
magicDictionary.buildDict(["hello", "leetcode"]);
magicDictionary.search(“hello”); // Return False
magicDictionary.search(“hhllo”); // Replacing the second 'h' with 'e' can match "hello", so it returns True
magicDictionary.search(“hell”); // Return False
magicDictionary.search(“leetcoded”); // Return False
Tips:
- 1 <= dictionary.length <= 100
- 1 <= dictionary[i].length <= 100
- dictionary[i] consists of lowercase letters only
- All strings in the dictionary are different from each other
- 1 <= searchWord.length <= 100
- searchWord consists of lowercase letters only
- buildDict is called only once before search.
- search can be called up to 100 times
Idea:
Prefix tree + dfs
The initialization and insertion methods are the same as the previous two questions
Query method:
- If the end of the dictionary word is reached and the end of the query word is reached and the number of modifications = = 1, the query is successful
- Traverse 26 possible letters. If there is a true, return true directly
In the future, when passing parameters to functions, remember to add references to strings & just add
The efficiency is ten times worse and the memory is six times worse
Because there is no reference, dfs will create another formal parameter string when calling the function, which takes time and space. If you add a reference, you can directly take the passed string parameter, so you don't have to create it again
class Trie { public: vector<Trie*> next; bool isWord; Trie():next(26,nullptr),isWord(false){} void insert(const string& word) { Trie* node = this; for(auto& ch : word) { int index = ch - 'a'; if(node->next[index] == nullptr) { node->next[index] = new Trie(); } node = node->next[index]; } node->isWord = true; } }; class MagicDictionary { private: Trie* root; bool dfs(Trie* root, string& word, int index, int edit) { if(root == nullptr || edit > 1) return false; //If root is just at the end of the dictionary string, and index is just at the end of the string, and only one character has been modified if(root->isWord && index == word.size() && edit == 1) { return true; } if(index >= word.size()) return false; //It must be traversed. Otherwise, if hello and hello are queried directly, true will not be returned for(int i = 0; i < 26; i++) { //If the child node exists, edit remains unchanged; Otherwise, edit plus 1; int nextEdit = i == word[index] - 'a'? edit : edit + 1; // Traverse 26 types until true is returned if(dfs(root->next[i], word, index + 1, nextEdit)) return true; } return false; } public: /** Initialize your data structure here. */ MagicDictionary() { root = new Trie(); } void buildDict(vector<string> dictionary) { for(auto& word : dictionary) { root->insert(word); } } bool search(string searchWord) { return dfs(root, searchWord, 0, 0); } };