在问 · 算法题解

692. 前 K 个高频单词

中等栈与堆哈希表 + 堆力扣原题 ↗

题目描述

输入单词数组 words 和整数 k。

统计每个不同单词出现的次数,选出次数最多的 k 个单词返回。

结果按出现次数从多到少排列;次数相同的单词按字典序从小到大排列。

示例 1
输入:words = ["i","love","leetcode","i","love","coding"]
k = 2
输出:["i","love"]

解释:"i" 和 "love" 各出现 2 次,按字典序 "i" 在前。

示例 2
输入:words = ["the","day","is","sunny","the","the","the","sunny","is","is"]
k = 4
输出:["the","is","sunny","day"]

解释:四个单词的出现次数依次为 4、3、2、1。

数据范围

  • 1 <= words.length <= 500
  • 1 <= words[i].length <= 10
  • words[i] 只包含小写英文字母。
  • k 的取值范围是 [1, 不同单词的个数]。

进阶:能否做到时间 O(n log k)、空间 O(n)?

解题思路:哈希表 + 堆

先用哈希表统计每个单词的次数。然后把 (−次数, 单词) 放进最小堆:次数越大越靠前,次数相同时元组自然比较单词,字典序小的靠前,恰好就是题目要求的顺序。heapify 建堆是 O(m),再弹出 k 次就是答案。

为什么正确

堆顶始终是剩余单词中“次数最大,其次字典序最小”的那一个,所以依次弹出的就是排好序的结果。

复杂度分析

时间 O(n + m + k log m),n 为单词总数,m 为不同单词数;空间 O(m)。

易错点

次数取负是为了用最小堆模拟“次数大的优先”;但单词不能取负,字典序小的本来就应该优先。若用大小为 k 的堆优化,比较规则要完全反过来(次数小、字典序大的先被淘汰),最后还要反转。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def topKFrequent(self, words: List[str], k: int) -> List[str]:
        cnt = collections.Counter(words)
        heap = [(-c, w) for w, c in cnt.items()]
        heapq.heapify(heap)
        res = []
        for _ in range(k):
            res.append(heapq.heappop(heap)[1])
        return res

载入编辑器,自己跑一遍 →

其他解法

两种解法都先用哈希表计数,比较规则相同;排序一次排好全部单词,堆只弹出前 k 个。

解法 2:哈希表 + 排序 时间 O(n log n),空间 O(n)

先用哈希表统计每个单词的出现次数。再把所有不同单词排序:次数多的在前,次数相同时字典序小的在前。取前 k 个。

只需写好一个比较规则,代码最短。代价是对全部不同单词排序,k 远小于单词数时做了多余的工作。

class Solution:
    def topKFrequent(self, words: List[str], k: int) -> List[str]:
        cnt = collections.Counter(words)
        return sorted(cnt, key=lambda w: (-cnt[w], w))[:k]

栈与堆 专题的其他题目

查看 栈与堆 全部题目 →