🧠
Trie with Stream Buffer Limited to Max Word Length
💡 This approach further optimizes memory by limiting the stream buffer size to the longest word length, avoiding unnecessary traversal of irrelevant suffixes.
Intuition
Since no suffix longer than the longest word can match, keep only the last max word length characters in the buffer to reduce traversal time and memory.
Algorithm
- Find the maximum length of words in the list.
- Build a trie of reversed words as before.
- Maintain a stream buffer that stores only the last max word length characters.
- On each query, append the letter and remove oldest if buffer exceeds max length.
- Traverse the trie backwards through the buffer to check for suffix matches.
- Return true if a word is matched; otherwise, false.
💡 Limiting the buffer size reduces unnecessary checks and memory usage without affecting correctness.
class TrieNode:
def __init__(self):
self.children = {}
self.is_word = False
class StreamChecker:
def __init__(self, words):
self.root = TrieNode()
self.max_len = max(len(w) for w in words)
self.stream = []
for word in words:
node = self.root
for ch in reversed(word):
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.is_word = True
def query(self, letter):
self.stream.append(letter)
if len(self.stream) > self.max_len:
self.stream.pop(0)
node = self.root
for ch in reversed(self.stream):
if ch not in node.children:
return False
node = node.children[ch]
if node.is_word:
return True
return False
# Driver code
if __name__ == "__main__":
sc = StreamChecker(["cd", "f", "kl"])
queries = ['a','b','c','d','e','f','g','h','i','j','k','l']
results = [sc.query(ch) for ch in queries]
print(results)
Line Notes
self.max_len = max(len(w) for w in words)Calculate max word length to limit buffer
if len(self.stream) > self.max_lenRemove oldest character if buffer too long
self.stream.pop(0)Maintain buffer size within max length
for ch in reversed(self.stream)Traverse only relevant suffixes
node.is_word = TrueMark end of word in trie
return TrueReturn true if suffix matches
return FalseReturn false if no match
import java.util.*;
class StreamChecker {
private static class TrieNode {
Map<Character, TrieNode> children = new HashMap<>();
boolean isWord = false;
}
private TrieNode root;
private LinkedList<Character> stream;
private int maxLen;
public StreamChecker(String[] words) {
root = new TrieNode();
stream = new LinkedList<>();
maxLen = 0;
for (String word : words) {
maxLen = Math.max(maxLen, word.length());
TrieNode node = root;
for (int i = word.length() - 1; i >= 0; i--) {
char ch = word.charAt(i);
node.children.putIfAbsent(ch, new TrieNode());
node = node.children.get(ch);
}
node.isWord = true;
}
}
public boolean query(char letter) {
stream.addLast(letter);
if (stream.size() > maxLen) stream.removeFirst();
TrieNode node = root;
Iterator<Character> it = stream.descendingIterator();
while (it.hasNext()) {
char ch = it.next();
if (!node.children.containsKey(ch)) return false;
node = node.children.get(ch);
if (node.isWord) return true;
}
return false;
}
public static void main(String[] args) {
StreamChecker sc = new StreamChecker(new String[]{"cd", "f", "kl"});
char[] queries = {'a','b','c','d','e','f','g','h','i','j','k','l'};
List<Boolean> results = new ArrayList<>();
for (char ch : queries) {
results.add(sc.query(ch));
}
System.out.println(results);
}
}
Line Notes
maxLen = 0Initialize max length variable
maxLen = Math.max(maxLen, word.length())Update max length while inserting words
stream = new LinkedList<>()Use linked list for efficient removals
if (stream.size() > maxLen) stream.removeFirst()Remove oldest character to limit buffer size
Iterator<Character> it = stream.descendingIterator()Traverse stream backwards
if (!node.children.containsKey(ch)) return falseStop if no matching child
if (node.isWord) return trueReturn true if suffix matches
#include <iostream>
#include <unordered_map>
#include <vector>
#include <string>
#include <deque>
using namespace std;
struct TrieNode {
unordered_map<char, TrieNode*> children;
bool isWord = false;
};
class StreamChecker {
TrieNode* root;
deque<char> stream;
int maxLen;
public:
StreamChecker(vector<string>& words) {
root = new TrieNode();
maxLen = 0;
for (auto& word : words) {
maxLen = max(maxLen, (int)word.size());
TrieNode* node = root;
for (int i = (int)word.size() - 1; i >= 0; --i) {
char ch = word[i];
if (!node->children.count(ch))
node->children[ch] = new TrieNode();
node = node->children[ch];
}
node->isWord = true;
}
}
bool query(char letter) {
stream.push_back(letter);
if ((int)stream.size() > maxLen) stream.pop_front();
TrieNode* node = root;
for (auto it = stream.rbegin(); it != stream.rend(); ++it) {
char ch = *it;
if (!node->children.count(ch)) return false;
node = node->children[ch];
if (node->isWord) return true;
}
return false;
}
};
int main() {
vector<string> words = {"cd", "f", "kl"};
StreamChecker sc(words);
vector<char> queries = {'a','b','c','d','e','f','g','h','i','j','k','l'};
for (char ch : queries) {
cout << (sc.query(ch) ? "true" : "false") << " ";
}
cout << endl;
return 0;
}
Line Notes
maxLen = 0Initialize max word length
maxLen = max(maxLen, (int)word.size())Update max length while inserting words
deque<char> streamUse deque to efficiently pop front
if ((int)stream.size() > maxLen) stream.pop_front()Limit buffer size to max length
for (auto it = stream.rbegin(); it != stream.rend(); ++it)Traverse stream backwards
if (!node->children.count(ch)) return falseStop if no matching child
if (node->isWord) return trueReturn true if suffix matches
class TrieNode {
constructor() {
this.children = new Map();
this.isWord = false;
}
}
class StreamChecker {
constructor(words) {
this.root = new TrieNode();
this.stream = [];
this.maxLen = 0;
for (const word of words) {
this.maxLen = Math.max(this.maxLen, word.length);
let node = this.root;
for (let i = word.length - 1; i >= 0; i--) {
const ch = word[i];
if (!node.children.has(ch)) {
node.children.set(ch, new TrieNode());
}
node = node.children.get(ch);
}
node.isWord = true;
}
}
query(letter) {
this.stream.push(letter);
if (this.stream.length > this.maxLen) {
this.stream.shift();
}
let node = this.root;
for (let i = this.stream.length - 1; i >= 0; i--) {
const ch = this.stream[i];
if (!node.children.has(ch)) return false;
node = node.children.get(ch);
if (node.isWord) return true;
}
return false;
}
}
// Driver code
const sc = new StreamChecker(["cd", "f", "kl"]);
const queries = ['a','b','c','d','e','f','g','h','i','j','k','l'];
const results = queries.map(ch => sc.query(ch));
console.log(results);
Line Notes
this.maxLen = 0Initialize max word length
this.maxLen = Math.max(this.maxLen, word.length)Update max length while inserting words
this.stream = []Store queried characters
if (this.stream.length > this.maxLen) this.stream.shift()Remove oldest character to limit buffer
for (let i = this.stream.length - 1; i >= 0; i--)Traverse stream backwards
if (!node.children.has(ch)) return falseStop if no matching child
if (node.isWord) return trueReturn true if suffix matches
TimeO(Q * L) where Q is queries and L is max word length
SpaceO(W * L) for trie and O(L) for stream buffer
Limiting buffer size reduces memory and traversal length, improving performance slightly.
💡 This approach is the most practical and efficient for large inputs, balancing speed and memory.
Interview Verdict: Accepted
This is the best practical solution, showing mastery of trie optimization and stream handling.