Prefix and Suffix Search — Solution in Python, Java & C++
Prefix and Suffix Search solution explained: step-by-step approach, complexity and code in Python, Java, C++ and JavaScript. A hard problem.
Prefix and Suffix Search is a hard coding problem on arrays, strings and hash table, asked in Google and Meta interviews.
- Difficulty: Hard
- Topics: Arrays, Strings, Hash Table, Trie
- Asked at: Google, Meta
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
Build a dictionary that finds words by a given prefix and a given suffix at the same time.
Write the class WordFilter:
WordFilter(String[] words)stores the dictionary; wordiiswords[i].int f(String pref, String suff)returns the largest indexisuch thatwords[i]starts withprefand ends withsuff, or-1if no word does. A whole word counts as its own prefix and suffix.
The judge calls the methods in the order listed in the first input line, with the arguments in the second; the expected output holds each call's result, null for the constructor and for every method that returns nothing.
Example 1
Input: ["WordFilter","f","f","f","f","f"]
[[["dosa","dal","salad","sad"]],["d","a"],["sa","d"],["s","lad"],["da","sa"],["dosa","dosa"]]
Output: [null,0,3,2,-1,0]
Explanation: "dosa" starts with d and ends with a. Both "salad" and "sad" fit (sa…d); the larger index, 3, wins. Only "salad" ends with "lad". No word starts with "da" and ends with "sa".
Example 2
Input: ["WordFilter","f","f"]
[[["aa","aa"]],["a","a"],["aaa","a"]]
Output: [null,1,-1]
Explanation: The same word may appear twice; the later copy has the larger index. No word is long enough to start with "aaa".
Constraints
1 <= words.length <= 10^41 <= words[i].length <= 71 <= pref.length, suff.length <= 7words[i], pref and suff consist of lowercase English letters onlyAt most 10^4 calls are made to f
Hints for Prefix and Suffix Search
Hint 1
Words are at most 7 letters long, so each word has at most 7 non-empty prefixes and 7 non-empty suffixes — at most 49 (prefix, suffix) pairs.
Hint 2
Precompute an answer for every pair: a hash map from
prefix + '#' + suffixto the index of the word. Insert the words in order so a later word overwrites an earlier one.Hint 3
A trie works too: for each word insert
suffix + '{' + wordfor every suffix, storing the latest index in each node; a query walkssuff + '{' + pref.
How to solve Prefix and Suffix Search
Because words are tiny (length ≤ 7), we can afford to list every (prefix, suffix) pair of every word in advance and map it to the largest index that has it. Each query is then one hash lookup.
Approach
- For each word
wat indexi, in increasingi, and for every prefix lengthaand suffix lengthbfrom 1 tolen(w): setmap[w[0:a] + '#' + w[len-b:]] = i. f(pref, suff): returnmap[pref + '#' + suff]if present, otherwise-1.
Why it works
A word matches the query exactly when pref is one of its prefixes and suff one of its suffixes, i.e. when the key pref#suff was generated by it. Processing indices in increasing order and overwriting leaves the largest matching index in the map. The separator # cannot occur in a word, so different (prefix, suffix) pairs never produce the same key.
Complexity
- Time —
O(N · L^3) to build (N words, L ≤ 7, building each key costs O(L)); O(L) per query - Space —
O(N · L^3)
Pitfalls
- Without a separator,
"ab" + "c"and"a" + "bc"collide. - Return the largest index, so insert in order and let later words overwrite.
- A query whose prefix is longer than every word must return -1 — no special case is needed when only real prefixes are inserted.
Reference solution
Python
class WordFilter:
def __init__(self, words: List[str]):
self.best = {} # "prefix#suffix" -> largest index having both
for i, w in enumerate(words):
n = len(w)
for a in range(1, n + 1):
p = w[:a]
for b in range(1, n + 1):
self.best[p + "#" + w[n - b:]] = i
def f(self, pref: str, suff: str) -> int:
return self.best.get(pref + "#" + suff, -1)Java
class WordFilter {
private HashMap<String, Integer> best = new HashMap<>(); // "prefix#suffix" -> largest index
public WordFilter(String[] words) {
for (int i = 0; i < words.length; i++) {
String w = words[i];
int n = w.length();
for (int a = 1; a <= n; a++) {
String p = w.substring(0, a) + "#";
for (int b = 1; b <= n; b++) best.put(p + w.substring(n - b), i);
}
}
}
public int f(String pref, String suff) {
return best.getOrDefault(pref + "#" + suff, -1);
}
}C++
class WordFilter {
unordered_map<string, int> best; // "prefix#suffix" -> largest index having both
public:
WordFilter(vector<string>& words) {
for (int i = 0; i < (int)words.size(); i++) {
const string& w = words[i];
int n = w.size();
for (int a = 1; a <= n; a++) {
string p = w.substr(0, a) + "#";
for (int b = 1; b <= n; b++) best[p + w.substr(n - b)] = i;
}
}
}
int f(string pref, string suff) {
auto it = best.find(pref + "#" + suff);
return it == best.end() ? -1 : it->second;
}
};JavaScript
var WordFilter = function(words) {
this.best = new Map(); // "prefix#suffix" -> largest index having both
for (var i = 0; i < words.length; i++) {
var w = words[i], n = w.length;
for (var a = 1; a <= n; a++) {
var p = w.substring(0, a);
for (var b = 1; b <= n; b++) this.best.set(p + "#" + w.substring(n - b), i);
}
}
};
WordFilter.prototype.f = function(pref, suff) {
var r = this.best.get(pref + "#" + suff);
return r === undefined ? -1 : r;
};Also on the editorial tab: C, C#, Go, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.