HASHING › IMPLEMENTARY PROBLEMS
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →The rules are ordered, so a query must not be treated as having just one kind of identity. Its original spelling matters first, its lowercase spelling matters second, and its spelling with every vowel replaced by one common marker matters third. Each identity can be computed as a string and used as a key in a hash-based container.
While reading wordlist from left to right, save the first word for each lowercase key and each vowel-mask key. The first word is important because the problem asks for the first matching word, not an arbitrary one. For every query, test the three keys in rule order and stop at the first successful lookup.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O((W + Q) x L) expected | Each word is copied and examined a constant number of times to build its keys, and each query is examined the same way before a constant number of expected hash lookups. Hash collisions can make an individual lookup slower; in the worst collision-heavy shape, the hash operations can degrade toward linear work in the number of stored keys. |
| Space | O(W x L) extra | The set and two maps store up to a constant number of normalized or original strings for each word, and each stored string has length at most L. The returned answer array is required output and is excluded from the extra-space bound. The bound is still O(W x L) when many words have distinct keys; duplicate keys only reduce actual storage. |
#include <algorithm>
#include <cctype>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using namespace std;
class Solution {
private:
static string toLower(const string& s) {
string result = s;
for (char& c : result) {
c = static_cast<char>(tolower(static_cast<unsigned char>(c)));
}
return result;
}
static string vowelMask(const string& s) {
string result;
result.reserve(s.size());
for (char c : s) {
char lower = static_cast<char>(tolower(static_cast<unsigned char>(c)));
if (lower == 'a' || lower == 'e' || lower == 'i' ||
lower == 'o' || lower == 'u') {
result.push_back('*');
} else {
result.push_back(lower);
}
}
return result;
}
public:
vector<string> spellchecker(vector<string>& wordlist, vector<string>& queries) {
unordered_set<string> originalWords;
unordered_map<string, string> lowercaseWords;
unordered_map<string, string> vowelWords;
for (const string& word : wordlist) {
originalWords.insert(word);
string lower = toLower(word);
if (!lowercaseWords.count(lower)) {
lowercaseWords[lower] = word;
}
string mask = vowelMask(word);
if (!vowelWords.count(mask)) {
vowelWords[mask] = word;
}
}
vector<string> answer;
answer.reserve(queries.size());
for (const string& query : queries) {
if (originalWords.count(query)) {
answer.push_back(query);
continue;
}
string lower = toLower(query);
if (lowercaseWords.count(lower)) {
answer.push_back(lowercaseWords[lower]);
continue;
}
string mask = vowelMask(query);
if (vowelWords.count(mask)) {
answer.push_back(vowelWords[mask]);
continue;
}
answer.push_back("");
}
return answer;
}
};The two if checks used while building the maps are the part that preserves the statement's tie-breaking rule. A map assignment would be correct for deciding whether a key exists, but assigning every time would replace an earlier word with a later one. Keeping only the first insertion makes the lookup result independent of any later duplicate.