intcmp(char** a, char** b) { int valA = queryVal(cnt, *a), valB = queryVal(cnt, *b); if (valA != valB) { return valB - valA; } int lenA = strlen(*a), lenB = strlen(*b); int len = fmin(lenA, lenB); for (int i = 0; i < len; i++) { if ((*a)[i] != (*b)[i]) { return (*a)[i] - (*b)[i]; } } return lenA - lenB; }
char** topKFrequent(char** words, int wordsSize, int k, int* returnSize) { cnt = NULL; for (int i = 0; i < wordsSize; i++) { structHashTable* tmp; HASH_FIND_STR(cnt, words[i], tmp); if (tmp == NULL) { structHashTable* tmp =malloc(sizeof(struct HashTable)); tmp->key = words[i]; tmp->val = 1; HASH_ADD_KEYPTR(hh, cnt, tmp->key, strlen(tmp->key), tmp); } else { tmp->val++; } } char** ret = malloc(sizeof(char*) * HASH_COUNT(cnt)); *returnSize = 0;
时间复杂度:O(l \times n + l \times m \log m),其中 n 表示给定字符串序列的长度,l 表示字符串的平均长度,m 表示实际字符串种类数。我们需要 l \times n 的时间将字符串插入到哈希表中,以及 l \times m \log m 的时间完成字符串比较(最坏情况下所有字符串出现频率都相同,我们需要将它们两两比较)。
空间复杂度:O(l \times m),其中 l 表示字符串的平均长度,m 表示实际字符串种类数。哈希表和生成的排序数组空间占用均为 O(l \times m)。
方法二:优先队列
思路及算法
对于前 k 大或前 k 小这类问题,有一个通用的解法:优先队列。优先队列可以在 O(\log n) 的时间内完成插入或删除元素的操作(其中 n 为优先队列的大小),并可以 O(1) 地查询优先队列顶端元素。
在本题中,我们可以创建一个小根优先队列(顾名思义,就是优先队列顶端元素是最小元素的优先队列)。我们将每一个字符串插入到优先队列中,如果优先队列的大小超过了 k,那么我们就将优先队列顶端元素弹出。这样最终优先队列中剩下的 k 个元素就是前 k 个出现次数最多的单词。
type pair struct { w string c int } type hp []pair func(h hp) Len() int { returnlen(h) } func(h hp) Less(i, j int) bool { a, b := h[i], h[j]; return a.c < b.c || a.c == b.c && a.w > b.w } func(h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func(h *hp) Push(v interface{}) { *h = append(*h, v.(pair)) } func(h *hp) Pop() interface{} { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v }
functopKFrequent(words []string, k int) []string { cnt := map[string]int{} for _, w := range words { cnt[w]++ } h := &hp{} for w, c := range cnt { heap.Push(h, pair{w, c}) if h.Len() > k { heap.Pop(h) } } ans := make([]string, k) for i := k - 1; i >= 0; i-- { ans[i] = heap.Pop(h).(pair).w } return ans }
复杂度分析
时间复杂度:O(l \times n + m \times l \log k),其中 n 表示给定字符串序列的长度,m 表示实际字符串种类数,l 表示字符串的平均长度。我们需要 l \times n 的时间将字符串插入到哈希表中,以及每次插入元素到优先队列中都需要 l \log k 的时间,共需要插入 m 次。