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 | 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 | 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
| Operation | unordered_set | unordered_map |
|---|---|---|
| Insert | O(1) | O(1) |
| Lookup | O(1) | O(1) |
| Delete | O(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.
