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 implements O(n) lookup

Problem:

Initially, I used brute force search, but it TLE’d, as follows.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
#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 can reduce the time from O(n^2) to O(n). Below is the officially recommended C++ implementation.

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
#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 the mapping from the lowercased string to the original string        unordered_map<string, string> words_cap;        // Store the mapping from the string with all vowels converted to '*' to the original string        unordered_map<string, string> words_vow;        //Convert all vowels to '*'        string devowel(string word)        {                string ans;                for (char c : word) {                        ans += isVowel(c) ? '*' : c;                }                return ans;        }        // Check whether it is a vowel        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 vowels                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 the set and the 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, replace vowels with '*'                        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)
LookupO(1)O(1)
DeleteO(1)O(1)
  • Here O(1) Yes Average complexity, assuming the hash function is uniformly distributed and collisions are rare.
  • Main advantages:Faster than std::set/std::map (based on red-black trees), 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 into linked list
    • lookup, insertion, and deletion all become O(n)
  • C++ standard library implementations usually use bucket + linked list (or red-black tree), when there are too many collisions, it converts the linked list into a red-black tree, thereby 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, guaranteeing worst-case complexity O(log n)

Space complexity

  • Average O(n), storing the hash table and elements.
  • The hash table requires an additional bucket array, occupying extra space.
Loading comments…