Y
Published on

Leetcode Pattern 13 Trie

Authors
  • avatar
    Name
    Yinhuan Yuan
    Twitter

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:

  1. Root Node: The starting point of the Trie, which is an empty node without any character value.

  2. Children: Each node can have multiple children, corresponding to different characters.

  3. End Marker: Nodes may have an indicator (often a boolean flag) signifying the end of a valid word.

Trie has the following Key Operations:

  1. 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.

  2. 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.

  3. 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.

  4. 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)

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