- Published on
Leetcode Pattern 13 Trie
- Authors
- Name
- Yinhuan Yuan
Introduction
In computer science, a Trie (pronounced "try") is a powerful and versatile data structure used to efficiently store and manage a dynamic set of strings. It's particularly well-suited for prefix-based search operations, making it a popular choice for applications like autocomplete, spell checking, and dictionary implementations.
A Trie, also known as a prefix tree or digital tree, is a specialized tree-like data structure that stores strings in a way that allows for quick retrieval based on prefixes. Each node in a Trie represents a single character of a string, and the path from the root to a node spells out a prefix of some string in the set. The key features of a Trie include:
Root Node: The starting point of the Trie, which is an empty node without any character value.
Children: Each node can have multiple children, corresponding to different characters.
End Marker: Nodes may have an indicator (often a boolean flag) signifying the end of a valid word.
Trie has the following Key Operations:
Insertion: Adding a word to a Trie involves iterating through each character of the word and creating a node if it doesn't already exist. The last character of the word is marked to indicate the end of the word.
Search: To search for a word, traverse the Trie by following the nodes corresponding to each character in the word. If you reach the end of the word with the end marker flag set, the word exists in the Trie.
Prefix Search: To find all words starting with a given prefix, traverse the Trie up to the end of the prefix. Then, recursively collect all words that branch out from that point.
Deletion: Removing a word involves unmarking the end marker flag and optionally removing nodes that are no longer part of any other word.
Trie has the following advantages:
- Efficient Search: Tries provide fast lookup times, especially for prefix-based searches, as they allow for character-by-character navigation.
- Space Efficiency: While Tries can use more memory than other structures like hash tables due to potentially many child nodes, they can be more space-efficient for storing a large number of short strings with common prefixes.
- Autocomplete and Suggestions: Tries are ideal for implementing features like autocomplete, where the structure can quickly provide all words starting with a given prefix.
Trie Insert Code
1. Initialize: cur = root
2. for each char c in target string S:
3. if cur does not have a child c:
4. cur.children[c] = new Trie node
5. cur = cur.children[c]
6. cur is the node which represents the string S
Trie Search Code
1. Initialize: cur = root
2. for each char c in target string S:
3. if cur does not have a child c:
4. search fails
5. cur = cur.children[c]
6. search successes
- Trie Questions
- 208.Implement Trie (Prefix Tree)
- 211. Design Add and Search Words Data Structure
- 212. Word Search II
- 336. Palindrome Pairs
- 588. Design In-Memory File System
- 1268. Search Suggestions System
- 677. Map Sum Pairs
- 648. Replace Words
- 642. Design Search Autocomplete System
- 421. Maximum XOR of Two Numbers in an Array
- 425. Word Squares
Trie Questions
208.Implement Trie (Prefix Tree)
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.
class TrieNode:
def __init__(self):
self.isEnd = False
self.children = {}
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
node = self.root
for ch in word:
if not (ch in node.children):
node.children[ch] = TrieNode()
node = node.children[ch]
node.isEnd = True
return
def search(self, word: str) -> bool:
node = self.root
for ch in word:
if ch in node.children:
node = node.children[ch]
else:
return False
return node.isEnd
def startsWith(self, prefix: str) -> bool:
node = self.root
for ch in prefix:
if ch in node.children:
node = node.children[ch]
else:
return False
return True
211. Design Add and Search Words Data Structure
Design a data structure that supports adding new words and finding if a string matches any previously added string.
Implement the WordDictionary class:
WordDictionary() Initializes the object. void addWord(word) Adds word to the data structure, it can be matched later. bool search(word) Returns true if there is any string in the data structure that matches word or false otherwise. word may contain dots '.' where dots can be matched with any letter.
Example:
Input ["WordDictionary","addWord","addWord","addWord","search","search","search","search"] [[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]] Output [null,null,null,null,false,true,true,true]
Explanation WordDictionary wordDictionary = new WordDictionary(); wordDictionary.addWord("bad"); wordDictionary.addWord("dad"); wordDictionary.addWord("mad"); wordDictionary.search("pad"); // return False wordDictionary.search("bad"); // return True wordDictionary.search(".ad"); // return True wordDictionary.search("b.."); // return True
Constraints:
1 <= word.length <= 25
word in addWord consists of lowercase English letters. word in search consist of '.' or lowercase English letters. There will be at most 3 dots in word for search queries. At most 104 calls will be made to addWord and search. Solution
class TrieNode:
def __init__(self):
self.children = {}
self.isEnd = False
class WordDictionary:
def __init__(self):
self.root = TrieNode()
def addWord(self, word: str) -> None:
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.isEnd = True
return
def search_helper(self, node: TrieNode, word: str) -> bool:
# Reach end
if not word:
return node.isEnd
char = word[0]
if char == ".":
for char in node.children:
if self.search_helper(node.children[char], word[1:]):
return True
elif char in node.children:
return self.search_helper(node.children[char], word[1:])
return False
def search(self, word: str) -> bool:
return self.search_helper(self.root, word)
212. Word Search II
Given an m x n board of characters and a list of strings words, return all words on the board.
Each word must be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.
Example 1:
Input: board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"] Output: ["eat","oath"] Example 2:
Input: board = [["a","b"],["c","d"]], words = ["abcb"] Output: []
Constraints:
m == board.length n == board[i].length 1 <= m, n <= 12
board[i][j] is a lowercase English letter. 1 <= words.length <= 3 \* 10^4
1 <= words[i].length <= 10
words[i] consists of lowercase English letters. All the strings of words are unique.
Solution
class TrieNode:
def __init__(self):
self.children = {}
self.isEnd = False
self.word = None
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str):
node = self.root
for ch in word:
if not (ch in node.children):
node.children[ch] = TrieNode()
node = node.children[ch]
node.isEnd = True
node.word = word
def search(self, word: str):
node = self.root
for ch in word:
if ch in node.children:
node = node.children[ch]
else:
return False
return node.isEnd
class Solution:
def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
trie = Trie()
for word in words:
trie.insert(word)
m = len(board)
n = len(board[0])
def getNeighbors(i, j):
ans = []
for diff in [(1, 0), (-1, 0), (0, -1), (0, 1)]:
n_i = i + diff[0]
n_j = j + diff[1]
if (n_i >= 0) and (n_i < m) and (n_j >= 0) and (n_j < n):
ans.append((n_i, n_j))
return ans
ans = set([])
def dfs(i, j, node):
c = board[i][j]
if c in node.children:
nextNode = node.children[c]
board[i][j] = " "
if nextNode.isEnd:
ans.add(nextNode.word)
neighbors = getNeighbors(i, j)
for neighbor in neighbors:
if board[neighbor[0]][neighbor[1]] != " ":
dfs(neighbor[0], neighbor[1], nextNode)
board[i][j] = c
for i in range(m):
for j in range(n):
dfs(i, j, trie.root)
return list(ans)
336. Palindrome Pairs
Given a list of unique words, return all the pairs of the distinct indices (i, j) in the given list, so that the concatenation of the two words words[i] + words[j] is a palindrome.
Example 1:
Input: words = ["abcd","dcba","lls","s","sssll"] Output: [[0,1],[1,0],[3,2],[2,4]] Explanation: The palindromes are ["dcbaabcd","abcddcba","slls","llssssll"] Example 2:
Input: words = ["bat","tab","cat"] Output: [[0,1],[1,0]] Explanation: The palindromes are ["battab","tabbat"] Example 3:
Input: words = ["a",""] Output: [[0,1],[1,0]]
Constraints:
1 <= words.length <= 5000
0 <= words[i].length <= 300
words[i] consists of lower-case English letters.
# check Pattern 21 Palindrome
class TrieNode:
def __init__(self):
self.children = {}
# Stores the index of the word if this node represents the last letter of a word.
self.word_end_index = -1
# Stores indices of words such that the substring from the current node to the end of the word forms a palindrome.
self.palindrome_suffix_indices = []
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word, index):
node = self.root
for i, char in enumerate(word):
if is_palindrome(word, i, len(word) - 1):
node.palindrome_suffix_indices.append(index)
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.word_end_index = index
# Finds all palindrome pairs that can be formed by the given word. It traverses the Trie and checks for palindromic conditions.
def find_palindromes_with_suffix(self, word, index):
result = []
current = self.root
for j in range(len(word)):
if current.word_end_index != -1 and current.word_end_index != index and is_palindrome(word, j, len(word) - 1):
result.append([index, current.word_end_index])
if word[j] not in current.children:
return result
current = current.children[word[j]]
for palindrome_index in current.palindrome_suffix_indices:
if palindrome_index != index:
result.append([index, palindrome_index])
if current.word_end_index != -1 and current.word_end_index != index:
result.append([index, current.word_end_index])
return result
# check the word[start: end + 1] is palindrome
def is_palindrome(word, start, end):
while start < end:
if word[start] != word[end]:
return False
start += 1
end -= 1
return True
class Solution:
def palindromePairs(self, words: List[str]) -> List[List[int]]:
trie = Trie()
for i, word in enumerate(words):
# word[::-1] reverse word
trie.insert(word[::-1], i)
pairs = []
for i, word in enumerate(words):
# extend: Add multiple results
pairs.extend(trie.find_palindromes_with_suffix(word, i))
return pairs
588. Design In-Memory File System
Design a data structure that simulates an in-memory file system.
Implement the FileSystem class:
FileSystem() Initializes the object of the system.
List<String> ls(String path)
If path is a file path, returns a list that only contains this file's name. If path is a directory path, returns the list of file and directory names in this directory. The answer should in lexicographic order. void mkdir(String path) Makes a new directory according to the given path. The given directory path does not exist. If the middle directories in the path do not exist, you should create them as well. void addContentToFile(String filePath, String content) If filePath does not exist, creates that file containing given content. If filePath already exists, appends the given content to original content. String readContentFromFile(String filePath) Returns the content in the file at filePath.
Example 1:
Input ["FileSystem", "ls", "mkdir", "addContentToFile", "ls", "readContentFromFile"] [[], ["/"], ["/a/b/c"], ["/a/b/c/d", "hello"], ["/"], ["/a/b/c/d"]] Output [null, [], null, null, ["a"], "hello"]
Explanation FileSystem fileSystem = new FileSystem(); fileSystem.ls("/"); // return [] fileSystem.mkdir("/a/b/c"); fileSystem.addContentToFile("/a/b/c/d", "hello"); fileSystem.ls("/"); // return ["a"] fileSystem.readContentFromFile("/a/b/c/d"); // return "hello"
Constraints:
1 <= path.length, filePath.length <= 100
path and filePath are absolute paths which begin with '/' and do not end with '/' except that the path is just "/". You can assume that all directory names and file names only contain lowercase letters, and the same names will not exist in the same directory. You can assume that all operations will be passed valid parameters, and users will not attempt to retrieve file content or list a directory or file that does not exist. 1 <= content.length <= 50
At most 300 calls will be made to ls, mkdir, addContentToFile, and readContentFromFile.
class TrieNode:
def __init__(self, name):
self.name = name
self.isFile = False
self.fileContent = ""
self.children = {}
class Trie(object):
def __init__(self):
self.root = TrieNode("")
def insert(self, path, isFile):
node = self.root
folders = path[1:].split("/")
for i in range(len(folders)):
if folders[i] in node.children:
node = node.children[folders[i]]
else:
new_node = TrieNode(folders[i])
node.children[folders[i]] = new_node
node = new_node
if isFile:
node.isFile = True
return node
def query(self, path):
node = self.root
folders = path[1:].split("/")
#print("query: {}".format(path))
for i in range(len(folders)):
if folders[i] in node.children:
node = node.children[folders[i]]
if node.isFile:
return [node.name]
filesFolders = list(node.children.keys())
#print(filesFolders)
filesFolders.sort()
return filesFolders
class FileSystem:
def __init__(self):
self.trie = Trie()
def ls(self, path: str) -> List[str]:
#print("ls: {}".format(path))
return self.trie.query(path)
def mkdir(self, path: str) -> None:
self.trie.insert(path, False)
def addContentToFile(self, filePath: str, content: str) -> None:
node = self.trie.insert(filePath, True)
node.fileContent += content
def readContentFromFile(self, filePath: str) -> str:
node = self.trie.insert(filePath, False)
return node.fileContent
# Your FileSystem object will be instantiated and called as such:
# obj = FileSystem()
# param_1 = obj.ls(path)
# obj.mkdir(path)
# obj.addContentToFile(filePath,content)
# param_4 = obj.readContentFromFile(filePath)
1268. Search Suggestions System
You are given an array of strings products and a string searchWord.
Design a system that suggests at most three product names from products after each character of searchWord is typed. Suggested products should have common prefix with searchWord. If there are more than three products with a common prefix return the three lexicographically minimums products.
Return a list of lists of the suggested products after each character of searchWord is typed.
Example 1:
Input: products = ["mobile","mouse","moneypot","monitor","mousepad"], searchWord = "mouse" Output: [["mobile","moneypot","monitor"],["mobile","moneypot","monitor"],["mouse","mousepad"],["mouse","mousepad"],["mouse","mousepad"]] Explanation: products sorted lexicographically = ["mobile","moneypot","monitor","mouse","mousepad"]. After typing m and mo all products match and we show user ["mobile","moneypot","monitor"]. After typing mou, mous and mouse the system suggests ["mouse","mousepad"]. Example 2:
Input: products = ["havana"], searchWord = "havana" Output: [["havana"],["havana"],["havana"],["havana"],["havana"],["havana"]] Explanation: The only word "havana" will be always suggested while typing the search word.
Constraints:
1 <= products.length <= 1000
1 <= products[i].length <= 3000
1 <= sum(products[i].length) <= 2 \* 104
All the strings of products are unique. products[i] consists of lowercase English letters. 1 <= searchWord.length <= 1000
searchWord consists of lowercase English letters.
Solution:
class TrieNode:
def __init__(self, char):
self.char = char
self.isEnd = False
self.count = 0
self.children = {}
class Trie(object):
def __init__(self):
self.root = TrieNode("")
def insert(self, word):
node = self.root
for ch in word:
if ch in node.children:
node = node.children[ch]
else:
new_node = TrieNode(ch)
node.children[ch] = new_node
node = new_node
node.isEnd = True
node.count += 1
def dfs(self, node, prefix):
if node.isEnd:
self.output.append((prefix + node.char, node.count))
for ch in node.children:
self.dfs(node.children[ch], prefix + node.char)
def query(self, prefix):
self.output = []
node = self.root
for ch in prefix:
if ch in node.children:
node = node.children[ch]
else:
return []
# remove last ch because node contains it.
self.dfs(node, prefix[:-1])
return self.output
class Solution:
def suggestedProducts(self, products: List[str], searchWord: str) -> List[List[str]]:
n = len(products)
trie = Trie()
words = set(products)
for i in range(n):
trie.insert(products[i])
ans = []
for i in range(len(searchWord)):
res = trie.query(searchWord[:i + 1])
res = list(map(lambda r: r[0], res))
res.sort()
if len(res) > 3:
res = res[:3]
#print(res)
ans = ans + [res]
return ans
677. Map Sum Pairs
Design a map that allows you to do the following:
Maps a string key to a given value. Returns the sum of the values that have a key with a prefix equal to a given string. Implement the MapSum class:
MapSum() Initializes the MapSum object. void insert(String key, int val) Inserts the key-val pair into the map. If the key already existed, the original key-value pair will be overridden to the new one. int sum(string prefix) Returns the sum of all the pairs' value whose key starts with the prefix.
Example 1:
Input ["MapSum", "insert", "sum", "insert", "sum"] [[], ["apple", 3], ["ap"], ["app", 2], ["ap"]] Output [null, null, 3, null, 5]
Explanation MapSum mapSum = new MapSum(); mapSum.insert("apple", 3); mapSum.sum("ap"); // return 3 (apple = 3) mapSum.insert("app", 2); mapSum.sum("ap"); // return 5 (apple + app = 3 + 2 = 5)
Constraints:
1 <= key.length, prefix.length <= 50
key and prefix consist of only lowercase English letters. 1 <= val <= 1000
At most 50 calls will be made to insert and sum.
class TrieNode:
def __init__(self):
self.isEnd = False
self.children = {}
self.val = None
class MapSum:
def __init__(self):
self.root = TrieNode()
def insert(self, key: str, val: int) -> None:
node = self.root
for ch in key:
if not (ch in node.children):
node.children[ch] = TrieNode()
node = node.children[ch]
node.isEnd = True
node.val = val
def calculateSum(self, node: TrieNode) -> int:
total = node.val if node.isEnd else 0
for ch in node.children:
total += self.calculateSum(node.children[ch])
return total
def sum(self, prefix: str) -> int:
node = self.root
for ch in prefix:
if ch in node.children:
node = node.children[ch]
else:
return 0
return self.calculateSum(node)
648. Replace Words
In English, we have a concept called root, which can be followed by some other word to form another longer word - let's call this word derivative. For example, when the root "help" is followed by the word "ful", we can form a derivative "helpful".
Given a dictionary consisting of many roots and a sentence consisting of words separated by spaces, replace all the derivatives in the sentence with the root forming it. If a derivative can be replaced by more than one root, replace it with the root that has the shortest length.
Return the sentence after the replacement.
Example 1:
Input: dictionary = ["cat","bat","rat"], sentence = "the cattle was rattled by the battery" Output: "the cat was rat by the bat" Example 2:
Input: dictionary = ["a","b","c"], sentence = "aadsfasf absbs bbab cadsfafs" Output: "a a b c"
Constraints:
1 <= dictionary.length <= 1000
1 <= dictionary[i].length <= 100
dictionary[i] consists of only lower-case letters. 1 <= sentence.length <= 106
sentence consists of only lower-case letters and spaces. The number of words in sentence is in the range [1, 1000] The length of each word in sentence is in the range [1, 1000] Every two consecutive words in sentence will be separated by exactly one space. sentence does not have leading or trailing spaces.
class TrieNode:
def __init__(self):
self.isEnd = False
self.children = {}
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
node = self.root
for ch in word:
if not (ch in node.children):
node.children[ch] = TrieNode()
node = node.children[ch]
node.isEnd = True
def search(self, word: str) -> str:
node = self.root
if not word[0] in node.children:
return word
ans = ""
isEnd = False
for ch in word:
if node.isEnd:
return ans
if not ch in node.children:
return word
node = node.children[ch]
ans += ch
return ans
class Solution:
def replaceWords(self, dictionary: List[str], sentence: str) -> str:
trie = Trie()
for word in dictionary:
trie.insert(word)
ans = []
for word in sentence.split(" "):
ans.append(trie.search(word))
return " ".join(ans)
642. Design Search Autocomplete System
Design a search autocomplete system for a search engine. Users may input a sentence (at least one word and end with a special character '#').
You are given a string array sentences and an integer array times both of length n where sentences[i] is a previously typed sentence and times[i] is the corresponding number of times the sentence was typed. For each input character except '#', return the top 3 historical hot sentences that have the same prefix as the part of the sentence already typed.
Here are the specific rules:
The hot degree for a sentence is defined as the number of times a user typed the exactly same sentence before. The returned top 3 hot sentences should be sorted by hot degree (The first is the hottest one). If several sentences have the same hot degree, use ASCII-code order (smaller one appears first). If less than 3 hot sentences exist, return as many as you can. When the input is a special character, it means the sentence ends, and in this case, you need to return an empty list. Implement the AutocompleteSystem class:
AutocompleteSystem(String[] sentences, int[] times) Initializes the object with the sentences and times arrays.
List<String> input(char c) This indicates that the user typed the character c.
Returns an empty array [] if c == '#' and stores the inputted sentence in the system. Returns the top 3 historical hot sentences that have the same prefix as the part of the sentence already typed. If there are fewer than 3 matches, return them all.
Example 1:
Input ["AutocompleteSystem", "input", "input", "input", "input"] [[["i love you", "island", "iroman", "i love leetcode"], [5, 3, 2, 2]], ["i"], [" "], ["a"], ["#"]] Output [null, ["i love you", "island", "i love leetcode"], ["i love you", "i love leetcode"], [], []]
Explanation AutocompleteSystem obj = new AutocompleteSystem(["i love you", "island", "iroman", "i love leetcode"], [5, 3, 2, 2]); obj.input("i"); // return ["i love you", "island", "i love leetcode"]. There are four sentences that have prefix "i". Among them, "ironman" and "i love leetcode" have same hot degree. Since ' ' has ASCII code 32 and 'r' has ASCII code 114, "i love leetcode" should be in front of "ironman". Also we only need to output top 3 hot sentences, so "ironman" will be ignored. obj.input(" "); // return ["i love you", "i love leetcode"]. There are only two sentences that have prefix "i ". obj.input("a"); // return []. There are no sentences that have prefix "i a". obj.input("#"); // return []. The user finished the input, the sentence "i a" should be saved as a historical sentence in system. And the following input will be counted as a new search.
Constraints:
n == sentences.length n == times.length 1 <= n <= 100
1 <= sentences[i].length <= 100
1 <= times[i] <= 50
c is a lowercase English letter, a hash '#', or space ' '. Each tested sentence will be a sequence of characters c that end with the character '#'. Each tested sentence will have a length in the range [1, 200]. The words in each input sentence are separated by single spaces. At most 5000 calls will be made to input.
Explain: traverse
function will look for all the words starts with the prefix. Use results = sorted(results, key=lambda x: (-x[0], x[1]))
to sort the results.
class TrieNode:
def __init__(self):
self.isEnd = False
self.searchTime = 0
self.children = {}
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> TrieNode:
node = self.root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.isEnd = True
return node
def search(self, prefix: str) -> TrieNode:
node = self.root
for ch in prefix:
if ch in node.children:
node = node.children[ch]
else:
return None
return node
def traverse(self, node: TrieNode, prefix: str) -> int:
ans = []
if node.isEnd:
ans = [(node.searchTime, prefix)]
for ch in node.children:
ans += self.traverse(node.children[ch], prefix + ch)
return ans
class AutocompleteSystem:
def __init__(self, sentences, times):
n = len(sentences)
self.trie = Trie()
for i in range(n):
endNode = self.trie.insert(sentences[i])
endNode.searchTime = times[i]
self.searchString = ""
def input(self, c: str):
if (c == "#"):
node = self.trie.insert(self.searchString)
node.searchTime += 1
self.searchString = ""
return []
self.searchString += c
node = self.trie.search(self.searchString)
if node is None:
return []
results = self.trie.traverse(node, self.searchString)
results = sorted(results, key=lambda x: (-x[0], x[1]))
if len(results) > 3:
results = results[:3]
return list(map(lambda result: result[1], results))
421. Maximum XOR of Two Numbers in an Array
Given an integer array nums, return the maximum result of nums[i] XOR nums[j], where 0 <= i <= j < n
.
Example 1:
Input: nums = [3,10,5,25,2,8] Output: 28 Explanation: The maximum result is 5 XOR 25 = 28. Example 2:
Input: nums = [14,70,53,83,49,91,36,80,92,51,66,70] Output: 127
Constraints:
1 <= nums.length <= 2 \* 105
0 <= nums[i] <= 231 - 1
class TrieNode:
def __init__(self):
self.children = {}
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, num):
node = self.root
for i in range(31, -1, -1):
bit = (num >> i) & 1
if bit not in node.children:
node.children[bit] = TrieNode()
node = node.children[bit]
def find_max_xor(self, num):
node = self.root
max_xor = 0
for i in range(31, -1, -1):
bit = (num >> i) & 1
toggled_bit = 1 - bit
if toggled_bit in node.children:
max_xor = (max_xor << 1) | 1
node = node.children[toggled_bit]
else:
max_xor = max_xor << 1
node = node.children[bit]
return max_xor
class Solution:
def findMaximumXOR(self, nums: List[int]) -> int:
trie = Trie()
max_xor = 0
# Insert all numbers into the Trie
for num in nums:
trie.insert(num)
# Find the maximum XOR for each number
for num in nums:
max_xor = max(max_xor, trie.find_max_xor(num))
return max_xor
class Solution:
def findMaximumXOR(self, nums: List[int]) -> int:
# Initialize the maximum XOR value
max_xor = 0
# Initialize the mask
mask = 0
# Iterate over the bit positions from 31 to 0
for i in range(31, -1, -1):
# Update the mask to consider the next bit
mask |= (1 << i)
# Use a set to store prefixes of the numbers with the current mask
prefixes = set()
for num in nums:
prefixes.add(num & mask)
# Try to form the new maximum XOR with the current bit set to 1
new_max_xor = max_xor | (1 << i)
# Check if there are two prefixes that can form the new_max_xor
for prefix in prefixes:
if (prefix ^ new_max_xor) in prefixes:
max_xor = new_max_xor
break
return max_xor
425. Word Squares
Given an array of unique strings words, return all the word squares you can build from words. The same word from words can be used multiple times. You can return the answer in any order.
A sequence of strings forms a valid word square if the kth row and column read the same string, where 0 <= k < max
(numRows, numColumns).
For example, the word sequence ["ball","area","lead","lady"] forms a word square because each word reads the same both horizontally and vertically.
Example 1:
Input: words = ["area","lead","wall","lady","ball"] Output: [["ball","area","lead","lady"],["wall","area","lead","lady"]] Explanation: The output consists of two word squares. The order of output does not matter (just the order of words in each word square matters). Example 2:
Input: words = ["abat","baba","atan","atal"] Output: [["baba","abat","baba","atal"],["baba","abat","baba","atan"]] Explanation: The output consists of two word squares. The order of output does not matter (just the order of words in each word square matters).
Constraints:
1 <= words.length <= 1000
1 <= words[i].length <= 4
All words[i] have the same length. words[i] consists of only lowercase English letters. All words[i] are unique.
class TrieNode:
def __init__(self):
self.isEnd = False
self.children = {}
self.words = []
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str):
node = self.root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.words.append(word)
node.isEnd = True
def search_words_with_prefix(self, prefix: str) -> bool:
node = self.root
for ch in prefix:
if ch not in node.children:
return []
node = node.children[ch]
return node.words
class Solution:
def wordSquares(self, words: List[str]) -> List[List[str]]:
trie = Trie()
for word in words:
trie.insert(word)
results = []
n = len(words[0])
def build_square(step):
if step == n:
results.append(square[:])
return
prefix = ''.join(square[i][step] for i in range(step))
for candidate in trie.search_words_with_prefix(prefix):
square.append(candidate)
build_square(step + 1)
square.pop()
square = []
for word in words:
square.append(word)
build_square(1)
square.pop()
return results