Whenever we come across questions with multiple strings, it is best to think if Trie can help us. - https://leetcode.com/problems/search-suggestions-system/solution/
put Node into dict
(e.g. defaultdict(Node))
// java
// LC 208
// V0
// IDEA : https://github.com/yennanliu/CS_basics/blob/master/leetcode_python/Tree/implement-trie-prefix-tree.py#L49
// modified by GPT
class TrieNode {
/** NOTE !!!!
*
* Define children as map structure:
*
* Map<String, TrieNode>
*
* -> string : current "element"
* -> TrieNode : the child object
*
*/
Map<String, TrieNode> children;
boolean isWord;
public TrieNode() {
= new HashMap<>();
children = false;
isWord }
}
/**
* NOTE !!!
*
* Define 2 classes
*
* 1) TrieNode
* 2) Trie
*
*/
class Trie {
;
TrieNode root
public Trie() {
= new TrieNode();
root }
public void insert(String word) {
/**
* NOTE !!! get current node first
*/
= root;
TrieNode cur for (String c : word.split("")) {
.children.putIfAbsent(c, new TrieNode());
cur// move node to its child
= cur.children.get(c);
cur }
.isWord = true;
cur}
public boolean search(String word) {
/**
* NOTE !!! get current node first
*/
= root;
TrieNode cur for (String c : word.split("")) {
= cur.children.get(c);
cur if (cur == null) {
return false;
}
}
return cur.isWord;
}
public boolean startsWith(String prefix) {
/**
* NOTE !!! get current node first
*/
= root;
TrieNode cur for (String c : prefix.split("")) {
= cur.children.get(c);
cur if (cur == null) {
return false;
}
}
return true;
}
}
# python
#--------------------------
# V1
#--------------------------
from collections import defaultdict
### NOTE : need to define our Node
class Node():
def __init__(self):
"""
NOTE : we use defaultdict(Node) as our trie data structure
-> use defaultdict
- key : every character from word
- value : Node (Node type)
and link children with parent Node via defaultdict
"""
self.children = defaultdict(Node)
self.isword = False
# implement basic methods
class Trie():
def __init__(self):
self.root = Node()
def insert(self, word):
### NOTE : we always start from below
= self.root
cur for w in word:
= cur.children[w] # same as self.root.defaultdict[w]
cur = True
cur.isword
def search(self, word):
### NOTE : we always start from below
= self.root
cur for w in word:
= cur.children.get(w)
cur if not cur:
return False
### NOTE : need to check if isword
return cur.isword
def startsWith(self, prefix):
### NOTE : we always start from below
= self.root
cur for p in prefix:
= cur.children.get(p)
cur if not cur:
return False
return True
# 208. Implement Trie (Prefix Tree)
# V0
# IDEA : trie concept : dict + tree
# https://blog.csdn.net/fuxuemingzhu/article/details/79388432
### NOTE : we need implement Node class
from collections import defaultdict
class Node(object):
def __init__(self):
### NOTE : we use defaultdict as dict
# TODO : make a default py dict version
self.children = defaultdict(Node)
self.isword = False
class Trie(object):
def __init__(self):
"""
Initialize your data structure here.
"""
### NOTE : we use the Node class we implement above
self.root = Node()
def insert(self, word):
= self.root
current for w in word:
= current.children[w]
current ### NOTE : if insert OP completed, mark isword attr as true
= True
current.isword
def search(self, word):
= self.root
current for w in word:
= current.children.get(w)
current if current == None:
return False
### NOTE : we need to check if isword atts is true (check if word terminated here as well)
return current.isword
def startsWith(self, prefix):
= self.root
current for w in prefix:
= current.children.get(w)
current if current == None:
return False
### NOTE : we don't need to check isword here, since it is "startsWith"
return True
// java
// LC 208
// V1
// https://leetcode.com/problems/implement-trie-prefix-tree/editorial/
class TrieNode {
// R links to node children
private TrieNode[] links;
private final int R = 26;
private boolean isEnd;
public TrieNode() {
= new TrieNode[R];
links }
public boolean containsKey(char ch) {
return links[ch -'a'] != null;
}
public TrieNode get(char ch) {
return links[ch -'a'];
}
public void put(char ch, TrieNode node) {
[ch -'a'] = node;
links}
public void setEnd() {
= true;
isEnd }
public boolean isEnd() {
return isEnd;
}
}
class Trie2 {
private TrieNode root;
public Trie2() {
= new TrieNode();
root }
// Inserts a word into the trie.
public void insert(String word) {
= root;
TrieNode node for (int i = 0; i < word.length(); i++) {
char currentChar = word.charAt(i);
if (!node.containsKey(currentChar)) {
.put(currentChar, new TrieNode());
node}
= node.get(currentChar);
node }
.setEnd();
node}
// search a prefix or whole key in trie and
// returns the node where search ends
private TrieNode searchPrefix(String word) {
= root;
TrieNode node for (int i = 0; i < word.length(); i++) {
char curLetter = word.charAt(i);
if (node.containsKey(curLetter)) {
= node.get(curLetter);
node } else {
return null;
}
}
return node;
}
// Returns if the word is in the trie.
public boolean search(String word) {
= searchPrefix(word);
TrieNode node 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) {
= searchPrefix(prefix);
TrieNode node return node != null;
}
}
# LC 211 Add and Search Word - Data structure design
# V0
from collections import defaultdict
class Node(object):
def __init__(self):
self.children = defaultdict(Node)
self.isword = False
class WordDictionary(object):
def __init__(self):
self.root = Node()
def addWord(self, word):
= self.root
current for w in word:
= current.children[w]
_next = _next
current = True
current.isword
def search(self, word):
return self.match(word, 0, self.root)
def match(self, word, index, root):
"""
NOTE : match is a helper func (for search)
- deal with 2 cases
- 1) word[index] != '.'
- 2) word[index] == '.'
"""
# note the edge cases
if root == None:
return False
if index == len(word):
return root.isword
# CASE 1: word[index] != '.'
if word[index] != '.':
return root != None and self.match(word, index + 1, root.children.get(word[index]))
# CASE 2: word[index] == '.'
else:
for child in root.children.values():
if self.match(word, index + 1, child):
return True
return False
// java
// LC 211
// V1
// IDEA : TRIE
// https://leetcode.com/problems/design-add-and-search-words-data-structure/editorial/
class TrieNode {
Map<Character, TrieNode> children = new HashMap();
boolean word = false;
public TrieNode() {}
}
class WordDictionary2 {
;
TrieNode trie
/** Initialize your data structure here. */
public WordDictionary2() {
= new TrieNode();
trie }
/** Adds a word into the data structure. */
public void addWord(String word) {
= trie;
TrieNode node
for (char ch : word.toCharArray()) {
if (!node.children.containsKey(ch)) {
.children.put(ch, new TrieNode());
node}
= node.children.get(ch);
node }
.word = true;
node}
/** Returns if the word is in the node. */
public boolean searchInNode(String word, TrieNode node) {
for (int i = 0; i < word.length(); ++i) {
char ch = word.charAt(i);
if (!node.children.containsKey(ch)) {
// if the current character is '.'
// check all possible nodes at this level
if (ch == '.') {
for (char x : node.children.keySet()) {
= node.children.get(x);
TrieNode child /** NOTE !!!
* -> if ".", we HAVE to go through all nodes in next levels
* -> and check if any of them is valid
* -> so we need to RECURSIVELY call searchInNode method with "i+1" sub string
*/
if (searchInNode(word.substring(i + 1), child)) {
return true;
}
}
}
// if no nodes lead to answer
// or the current character != '.'
return false;
} else {
// if the character is found
// go down to the next level in trie
= node.children.get(ch);
node }
}
return node.word;
}
/** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
public boolean search(String word) {
return searchInNode(word, trie);
}
}
# LC 1268. Search Suggestions System
# V1
# IDEA : TRIE
# https://leetcode.com/problems/search-suggestions-system/discuss/436183/Python-Trie-Solution
class TrieNode:
def __init__(self):
self.children = dict()
self.words = []
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
= self.root
node for char in word:
if char not in node.children:
= TrieNode()
node.children[char] = node.children[char]
node
node.words.append(word)
node.words.sort()while len(node.words) > 3:
node.words.pop()
def search(self, word):
= []
res = self.root
node for char in word:
if char not in node.children:
break
= node.children[char]
node
res.append(node.words[:])= len(word) - len(res)
l_remain for _ in range(l_remain):
res.append([])return res
class Solution:
def suggestedProducts(self, products: List[str], searchWord: str):
= Trie()
trie for prod in products:
trie.insert(prod)return trie.search(searchWord)
// IDEA : trie + dfs
// https://leetcode.com/problems/search-suggestions-system/solution/
// Custom class Trie with function to get 3 words starting with given prefix
class Trie {
// Node definition of a trie
class Node {
boolean isWord = false;
List<Node> children = Arrays.asList(new Node[26]);
};
Node Root, curr;
List<String> resultBuffer;
// Runs a DFS on trie starting with given prefix and adds all the words in the resultBuffer, limiting result size to 3
void dfsWithPrefix(Node curr, String word) {
if (resultBuffer.size() == 3)
return;
if (curr.isWord)
.add(word);
resultBuffer
// Run DFS on all possible paths.
for (char c = 'a'; c <= 'z'; c++)
if (curr.children.get(c - 'a') != null)
dfsWithPrefix(curr.children.get(c - 'a'), word + c);
}
Trie() {
= new Node();
Root }
// Inserts the string in trie.
void insert(String s) {
// Points curr to the root of trie.
= Root;
curr for (char c : s.toCharArray()) {
if (curr.children.get(c - 'a') == null)
.children.set(c - 'a', new Node());
curr= curr.children.get(c - 'a');
curr }
// Mark this node as a completed word.
.isWord = true;
curr}
List<String> getWordsStartingWith(String prefix) {
= Root;
curr = new ArrayList<String>();
resultBuffer // Move curr to the end of prefix in its trie representation.
for (char c : prefix.toCharArray()) {
if (curr.children.get(c - 'a') == null)
return resultBuffer;
= curr.children.get(c - 'a');
curr }
dfsWithPrefix(curr, prefix);
return resultBuffer;
}
};
class Solution {
List<List<String>> suggestedProducts(String[] products,
String searchWord) {
= new Trie();
Trie trie List<List<String>> result = new ArrayList<>();
// Add all words to trie.
for (String w : products)
.insert(w);
trieString prefix = new String();
for (char c : searchWord.toCharArray()) {
+= c;
prefix .add(trie.getWordsStartingWith(prefix));
result}
return result;
}
};
# LC 79. Word Search
# V0
# IDEA : DFS + backtracking
class Solution(object):
def exist(self, board, word):
### NOTE : construct the visited matrix
= [[False for j in range(len(board[0]))] for i in range(len(board))]
visited
"""
NOTE !!!! : we visit every element in board and trigger the dfs
"""
for i in range(len(board)):
for j in range(len(board[0])):
if self.dfs(board, word, 0, i, j, visited):
return True
return False
def dfs(self, board, word, cur, i, j, visited):
# if "not false" till cur == len(word), means we already found the wprd in board
if cur == len(word):
return True
### NOTE this condition
# 1) if idx out of range
# 2) if already visited
# 3) if board[i][j] != word[cur] -> not possible to be as same as word
if i < 0 or i >= len(board) or j < 0 or j >= len(board[0]) or visited[i][j] or board[i][j] != word[cur]:
return False
# NOTE THIS !! : mark as visited
= True
visited[i][j] ### NOTE THIS TRICK (run the existRecu on 4 directions on the same time)
= self.dfs(board, word, cur + 1, i + 1, j, visited) or\
result self.dfs(board, word, cur + 1, i - 1, j, visited) or\
self.dfs(board, word, cur + 1, i, j + 1, visited) or\
self.dfs(board, word, cur + 1, i, j - 1, visited)
# mark as non-visited
= False
visited[i][j]
return result
# LC 212. Word Search II
# V0
# IDEA : DFS + trie
# DEMO
# >>> words = ['oath', 'pea', 'eat', 'rain'], trie = {'o': {'a': {'t': {'h': {'#': None}}}}, 'p': {'e': {'a': {'#': None}}}, 'e': {'a': {'t': {'#': None}}}, 'r': {'a': {'i': {'n': {'#': None}}}}}
class Solution(object):
def checkList(self, board, row, col, word, trie, rList):
if row<0 or row>=len(board) or col<0 or col>=len(board[0]) or board[row][col] == '.' or board[row][col] not in trie:
return
= board[row][col]
c = word + c
_wordif '#' in trie[c]:
rList.add(_word)if len(trie[c]) == 1:
return # if next node is empty, return as no there is no need to search further
= '.'
board[row][col] self.checkList(board, row-1, col, _word, trie[c], rList) #up
self.checkList(board, row+1, col, _word, trie[c], rList) #down
self.checkList(board, row, col-1, _word, trie[c], rList) #left
self.checkList(board, row, col+1, _word, trie[c], rList) #right
= c
board[row][col]
def findWords(self, board, words):
if not board or not words:
return []
# building Trie
= {}, set()
trie, rList for word in words:
= trie
t for c in word:
if c not in t:
= {}
t[c] = t[c]
t '#'] = None
t[#print (">>> words = {}, trie = {}".format(words, trie))
for row in range(len(board)):
for col in range(len(board[0])):
if board[row][col] in trie:
self.checkList(board, row, col, "", trie, rList)
return list(rList)
# V1
# IDEA : Backtracking with Trie
# https://leetcode.com/problems/word-search-ii/solution/
class Solution:
def findWords(self, board, words):
= '$'
WORD_KEY
= {}
trie for word in words:
= trie
node for letter in word:
# retrieve the next node; If not found, create a empty node.
= node.setdefault(letter, {})
node # mark the existence of a word in trie node
= word
node[WORD_KEY]
= len(board)
rowNum = len(board[0])
colNum
= []
matchedWords
def backtracking(row, col, parent):
= board[row][col]
letter = parent[letter]
currNode
# check if we find a match of word
= currNode.pop(WORD_KEY, False)
word_match if word_match:
# also we removed the matched word to avoid duplicates,
# as well as avoiding using set() for results.
matchedWords.append(word_match)
# Before the EXPLORATION, mark the cell as visited
= '#'
board[row][col]
# Explore the neighbors in 4 directions, i.e. up, right, down, left
for (rowOffset, colOffset) in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
= row + rowOffset, col + colOffset
newRow, newCol if newRow < 0 or newRow >= rowNum or newCol < 0 or newCol >= colNum:
continue
if not board[newRow][newCol] in currNode:
continue
backtracking(newRow, newCol, currNode)
# End of EXPLORATION, we restore the cell
= letter
board[row][col]
# Optimization: incrementally remove the matched leaf node in Trie.
if not currNode:
parent.pop(letter)
for row in range(rowNum):
for col in range(colNum):
# starting from each of the cells
if board[row][col] in trie:
backtracking(row, col, trie)
return matchedWords
# V1'
# IDEA : DFS + trie
# https://leetcode.com/problems/word-search-ii/discuss/59808/Python-DFS-362ms
class Solution(object):
def checkList(self, board, row, col, word, trie, rList):
if row<0 or row>=len(board) or col<0 or col>=len(board[0]) or board[row][col] == '.' or board[row][col] not in trie: return
= board[row][col]
c = word + c
_wordif '#' in trie[c]:
rList.add(_word)if len(trie[c]) == 1: return # if next node is empty, return as no there is no need to search further
= '.'
board[row][col] self.checkList(board, row-1, col, _word, trie[c], rList) #up
self.checkList(board, row+1, col, _word, trie[c], rList) #down
self.checkList(board, row, col-1, _word, trie[c], rList) #left
self.checkList(board, row, col+1, _word, trie[c], rList) #right
= c
board[row][col]
def findWords(self, board, words):
"""
:type board: List[List[str]]
:type words: List[str]
:rtype: List[str]
"""
if not board or not words: return []
# building Trie
= {}, set()
trie, rList for word in words:
= trie
t for c in word:
if c not in t: t[c] = {}
= t[c]
t '#'] = None
t[for row in range(len(board)):
for col in range(len(board[0])):
if board[row][col] not in trie: continue
self.checkList(board, row, col, "", trie, rList)
return list(rList)