Cover image for LeetCode Daily Problem P966 Vowel Spellchecker

LeetCode Daily Problem P966 Vowel Spellchecker


Timeline

timeline

2025-09-15

init

unordered_map and unordered_set achieves O(n) lookup

Problem:

Initially used brute force search, but got TLE, as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

bool isVowel(char c)
{
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'A' || c == 'E' ||
c == 'I' || c == 'O' || c == 'U') {
return true;
}
return false;
}

int strcomplexcmp(const char *str1, const char *str2)
{
int len1, len2;
int i;

len1 = strlen(str1);
len2 = strlen(str2);
for (i = 0; i < len1 && i < len2; i++) {
if (str1[i] == str2[i]) {
continue;
} else if (isVowel(str1[i]) && isVowel(str2[i])) {
continue;
} else if (tolower(str1[i]) == tolower(str2[i])) {
continue;
} else {
return str1[i] - str2[i];
}
}
if (str1[i] == '\0' && str2[i] == '\0') {
return 0;
} else {
return str1[i] - str2[i];
}
}

/**
* Note: The returned array must be malloced, assume caller calls free().
*/
char **spellchecker(char **wordlist, int wordlistSize, char **queries, int queriesSize,
int *returnSize)
{
char **returnArray;
char *totally_matched;
char *case_matched;
char *vowel_matched;
char *complex_matched;

*returnSize = queriesSize;
returnArray = (char **)malloc(queriesSize * sizeof(char *));

for (int i = 0; i < queriesSize; i++) {
totally_matched = NULL;
case_matched = NULL;
vowel_matched = NULL;
complex_matched = NULL;

for (int j = 0; j < wordlistSize; j++) {
if (strcmp(wordlist[j], queries[i]) == 0 && totally_matched == NULL) {
totally_matched = wordlist[j];
break;
} else if (strcasecmp(wordlist[j], queries[i]) == 0 &&
case_matched == NULL) {
case_matched = wordlist[j];
} else if (strcomplexcmp(wordlist[j], queries[i]) == 0 &&
complex_matched == NULL) {
complex_matched = wordlist[j];
}
}

if (totally_matched) {
returnArray[i] = strdup(totally_matched);
} else if (case_matched) {
returnArray[i] = strdup(case_matched);
} else if (complex_matched) {
returnArray[i] = strdup(complex_matched);
} else {
returnArray[i] = strdup("");
}
}
return returnArray;
}

int main()
{
char *wordlist[] = { "KiTe", "kite", "hare", "Hare" };
int wordlistSize = 4;
char *queries[] = { "kite", "Kite", "KiTe", "Hare", "HARE",
"Hear", "hear", "keti", "keet", "keto" };

int queriesSize = 10;
int returnSize = queriesSize;
char **returnArray;
returnArray = spellchecker(wordlist, wordlistSize, queries, queriesSize, &returnSize);
for (int i = 0; i < returnSize; i++) {
printf("%s, ", returnArray[i]);
free(returnArray[i]);
}
free(returnArray);
}

Using hash table lookup reduces from O(n^2) to O(n), below is the officially recommended C++ implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <ctype.h>
using namespace std;

class Solution {
private:
// Store unprocessed strings
unordered_set<string> words_perfect;
// Store mapping of lowercased strings to original strings
unordered_map<string, string> words_cap;
// Store mapping of strings with vowels replaced by * to original strings
unordered_map<string, string> words_vow;

//Replace all vowels with *
string devowel(string word)
{
string ans;
for (char c : word) {
ans += isVowel(c) ? '*' : c;
}
return ans;
}
// Determine whether it is a vowel letter
bool isVowel(char c)
{
c = tolower(c);
return (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u');
}

string match(string query)
{
//Exact match
if (words_perfect.count(query)) {
return query;
}
// Case
string queryL;
for (char c : query) {
queryL += tolower(c);
}
if (words_cap.count(queryL)) {
return words_cap[queryL];
}
// After converting all to lowercase, find vowel letters
string queryLV = devowel(queryL);
if (words_vow.count(queryLV)) {
return words_vow[queryLV];
}

return "";
}

public:
vector<string> spellchecker(vector<string> &wordlist, vector<string> &queries)
{
//Fill set and two maps
for (string word : wordlist) {
words_perfect.insert(word);
string wordlow;
for (char c : word) {
wordlow += tolower(c);
}
if (!words_cap.count(wordlow)) {
words_cap[wordlow] = word;
}
// After converting all to lowercase, then convert vowel letters to *
string wordlowDV = devowel(wordlow);
if (!words_vow.count(wordlowDV)) {
words_vow[wordlowDV] = word;
}
}

vector<string> ans;
for (string query : queries) {
ans.push_back(match(query));
}
return ans;
}
};

Average time complexity

Operationunordered_setunordered_map
InsertO(1)O(1)
SearchO(1)O(1)
DeleteO(1)O(1)
  • HereO(1)isAverage complexity, assuming the hash function is evenly distributed and collisions are few.
  • Main advantages:Faster than std::set/std::map (based on red-black tree), red-black tree lookup is O(log n).

Worst-case time complexity

  • In the worst case, if all elements are hashed to the same bucket (hash collision), it degenerates intolinked list
    • lookup, insertion, deletion all becomeO(n)
  • C++ standard library implementations typically usebucket + linked list (or red-black tree), when there are too many collisions, the linked list is converted into a red-black tree, thus reducing the worst-case complexity:
    • After C++11, unordered_map/unordered_set’s single bucket linked list length exceeds a certain threshold (usually 8), it will be converted into a red-black tree, ensuring worst-case complexityO(log n)

Space complexity

  • Average O(n), storing hash table and elements.
  • Hash table requires an extra bucket array, occupying extra space.