Vowel Spellchecker
Look up queries against a wordlist with three fallback levels — exact, case-insensitive, vowel-mask.
Problem#
Given wordlist and queries, for each query return the matching word using this priority: (1) exact match; (2) case-insensitive match (return first such word in wordlist); (3) vowel-error match where vowels can be replaced by any vowel (case-insensitive, return first match). Otherwise "".
Examples#
wordlist = ["KiTe","kite","hare","Hare"], queries = ["kite","Kite","KiTe","Hare","HARE","Hear","hear","keti","keet","keto"]→["kite","KiTe","KiTe","Hare","hare","","","KiTe","","KiTe"].
Constraints#
1 <= wordlist.length, queries.length <= 5000,1 <= word.length <= 7.
Hints#
Hint 1
Hint 2
* after lowercasing. Approach#
Build three structures from wordlist (in order, only inserting the first occurrence in the maps). For each query: check exact set; else lowercase and check; else vowel-mask and check.
Solution#
class Solution { string lower(string s) { for (char& c : s) c = tolower(c); return s; } string mask(string s) { s = lower(std::move(s)); for (char& c : s) if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') c = '*'; return s; }public: vector<string> spellchecker(vector<string>& wordlist, vector<string>& queries) { unordered_set<string> exact(wordlist.begin(), wordlist.end()); unordered_map<string, string> caseMap, vowelMap; for (auto& w : wordlist) { string lo = lower(w); if (!caseMap.count(lo)) caseMap[lo] = w; string mk = mask(w); if (!vowelMap.count(mk)) vowelMap[mk] = w; } vector<string> ans; ans.reserve(queries.size()); for (auto& q : queries) { if (exact.count(q)) { ans.push_back(q); continue; } string lo = lower(q); auto it = caseMap.find(lo); if (it != caseMap.end()) { ans.push_back(it->second); continue; } string mk = mask(q); auto it2 = vowelMap.find(mk); ans.push_back(it2 != vowelMap.end() ? it2->second : ""); } return ans; }};import "strings"
func spellchecker(wordlist []string, queries []string) []string { isVowel := func(c byte) bool { return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' } mask := func(s string) string { lo := strings.ToLower(s) b := []byte(lo) for i := range b { if isVowel(b[i]) { b[i] = '*' } } return string(b) } exact := map[string]bool{} for _, w := range wordlist { exact[w] = true } caseMap := map[string]string{} vowelMap := map[string]string{} for _, w := range wordlist { lo := strings.ToLower(w) if _, ok := caseMap[lo]; !ok { caseMap[lo] = w } mk := mask(w) if _, ok := vowelMap[mk]; !ok { vowelMap[mk] = w } } ans := make([]string, 0, len(queries)) for _, q := range queries { if exact[q] { ans = append(ans, q) continue } lo := strings.ToLower(q) if v, ok := caseMap[lo]; ok { ans = append(ans, v) continue } mk := mask(q) if v, ok := vowelMap[mk]; ok { ans = append(ans, v) } else { ans = append(ans, "") } } return ans}from typing import List
class Solution: def spellchecker(self, wordlist: List[str], queries: List[str]) -> List[str]: vowels = set("aeiou")
def mask(s: str) -> str: return ''.join('*' if c in vowels else c for c in s.lower())
exact = set(wordlist) case_map: dict[str, str] = {} vowel_map: dict[str, str] = {} for w in wordlist: lo = w.lower() if lo not in case_map: case_map[lo] = w mk = mask(w) if mk not in vowel_map: vowel_map[mk] = w
ans: List[str] = [] for q in queries: if q in exact: ans.append(q) continue lo = q.lower() if lo in case_map: ans.append(case_map[lo]) continue mk = mask(q) ans.append(vowel_map.get(mk, "")) return ansfunction spellchecker(wordlist, queries) { const vowels = new Set(['a', 'e', 'i', 'o', 'u']); const mask = (s) => s.toLowerCase().split('').map((c) => vowels.has(c) ? '*' : c).join(''); const exact = new Set(wordlist); const caseMap = new Map(); const vowelMap = new Map(); for (const w of wordlist) { const lo = w.toLowerCase(); if (!caseMap.has(lo)) caseMap.set(lo, w); const mk = mask(w); if (!vowelMap.has(mk)) vowelMap.set(mk, w); } const ans = []; for (const q of queries) { if (exact.has(q)) { ans.push(q); continue; } const lo = q.toLowerCase(); if (caseMap.has(lo)) { ans.push(caseMap.get(lo)); continue; } const mk = mask(q); ans.push(vowelMap.has(mk) ? vowelMap.get(mk) : ""); } return ans;}class Solution { public String[] spellchecker(String[] wordlist, String[] queries) { Set<String> exact = new HashSet<>(Arrays.asList(wordlist)); Map<String, String> caseMap = new HashMap<>(); Map<String, String> vowelMap = new HashMap<>(); for (String w : wordlist) { String lo = w.toLowerCase(); caseMap.putIfAbsent(lo, w); String mk = mask(lo); vowelMap.putIfAbsent(mk, w); } String[] ans = new String[queries.length]; for (int i = 0; i < queries.length; i++) { String q = queries[i]; if (exact.contains(q)) { ans[i] = q; continue; } String lo = q.toLowerCase(); if (caseMap.containsKey(lo)) { ans[i] = caseMap.get(lo); continue; } String mk = mask(lo); ans[i] = vowelMap.getOrDefault(mk, ""); } return ans; }
private String mask(String s) { char[] arr = s.toCharArray(); for (int i = 0; i < arr.length; i++) { char c = arr[i]; if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') { arr[i] = '*'; } } return new String(arr); }}function spellchecker(wordlist: string[], queries: string[]): string[] { const vowels = new Set<string>(['a', 'e', 'i', 'o', 'u']); const mask = (s: string): string => s.toLowerCase().split('').map((c) => vowels.has(c) ? '*' : c).join(''); const exact = new Set<string>(wordlist); const caseMap = new Map<string, string>(); const vowelMap = new Map<string, string>(); for (const w of wordlist) { const lo = w.toLowerCase(); if (!caseMap.has(lo)) caseMap.set(lo, w); const mk = mask(w); if (!vowelMap.has(mk)) vowelMap.set(mk, w); } const ans: string[] = []; for (const q of queries) { if (exact.has(q)) { ans.push(q); continue; } const lo = q.toLowerCase(); if (caseMap.has(lo)) { ans.push(caseMap.get(lo)!); continue; } const mk = mask(q); ans.push(vowelMap.has(mk) ? vowelMap.get(mk)! : ""); } return ans;}Editorial#
Building each index in wordlist order with “first wins” satisfies the spec’s tiebreaker without sorting. The three layers cascade — exact wins over case-insensitive wins over vowel-error — and each layer is O(L) per query after preprocessing.
Complexity#
- Time: O((N + Q) * L).
- Space: O(N * L).
Concept revision#
Related#