《LeetCode 692 前K个高频单词 优先级队列解法》

一.题目

692. 前K个高频单词 - 力扣(LeetCode)

二.思路讲解

2.1 思路讲解

本题要求找出频率最高的 k 个单词,若频率相同则按字典序升序输出。

  • 首先,使用哈希表统计每个单词的出现次数。

  • 然后,我们需要从这些单词中选出频率最高的 k 个。这里采用小根堆来维护当前频率最高的 k 个单词。

    • 堆中元素为 pair<string, int>,分别表示单词及其出现次数。

    • 自定义比较规则:频率低的优先级高 (放在堆顶),频率相同时字典序大的优先级高 (即字典序大的更靠近堆顶)。这样堆顶始终是频率最小同频率字典序最大的单词,也就是当前候选中最"差"的一个。

    • 遍历哈希表,将每个单词加入堆。如果堆大小超过 k,则弹出堆顶(即淘汰当前最差的候选)。

    • 遍历结束后,堆中剩下的就是频率最高的 k 个单词,但顺序是频率升序、同频率字典序降序

  • 最后,将堆中的单词依次弹出,并从结果数组的末尾开始填充 (即逆序存放),这样最终数组就是按频率降序、同频率字典序升序的顺序,符合题目要求。

三.代码演示

cpp 复制代码
class Solution 
{
    typedef pair<string,int> P;   
    //仿函数
    struct cmp
    {
        bool operator()(const P& a,const P& b)
        {
            if(a.second == b.second)//频率相同,那么大的在下面
            {
                return a.first < b.first;
            }
            return a.second > b.second;
        }
    };
public:
    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        //1.统计一下每一个单词出现的个数
        unordered_map<string,int> hash;
        for(const auto& s:words)
            hash[s]++;
        //2.创建一个大小为K的堆
        priority_queue<P,vector<P>,cmp>heap;
        //3.TopK的逻辑
        for(const auto& ch : hash)
        {
            heap.push(ch);
            if(heap.size() > k)
                heap.pop();
        }
        //4提取结果
        vector<string> ret(k);//创建K个数组
        for(int i = k - 1;i >= 0;i--)
        {
            ret[i] = heap.top().first;
            heap.pop();
        }
        return ret;
    }
};

四.代码讲解

一、统计词频

首先,我们需要统计每个单词出现的次数。使用 unordered_map<string, int> 哈希表,遍历整个单词列表,将每个单词作为键,出现次数作为值进行累加。这一步完成后,哈希表中记录了所有单词及其对应的频率。

二、自定义比较仿函数

为了使用小根堆 来维护频率最高的 k 个单词,我们需要自定义堆中元素的比较规则。堆中存储的是 pair<string, int>,分别代表单词和频率。 比较规则

  • 频率低的元素应被视为"更小"(即优先级更高),这样堆顶就是当前 k 个候选中最小的频率。

  • 如果频率相同,则字典序大的元素应被视为"更小"(即优先级更高),这样堆顶就是同频率中字典序最大的单词。 这样,堆顶始终是当前候选中最"差"的一个(频率最小,同频率下字母顺序最靠后),方便我们在堆大小超过 k 时将其弹出。

在代码中,通过仿函数 cmp 重载 operator() 实现这一规则:

  • 当两个单词频率不同时,我们希望频率小的单词优先级更低 (更容易被弹出)。因此比较函数返回 a.second > b.second,这意味着如果 a 的频率大于 b,则 a 的优先级更高(即 a 更靠近堆底);反之,若 a 的频率小于 b,则 a 的优先级更低,会排在 b 后面(更靠近堆顶)。

  • 当两个单词频率相同时,我们希望字典序大的单词优先级更低 (更容易被弹出)。因此比较函数返回 a.first < b.first,这意味着如果 a 的字典序小于 b,则 a 的优先级更高(即 a 更靠近堆底);反之,若 a 的字典序大于 b,则 a 的优先级更低,会排在 b 后面(更靠近堆顶)。

三、小根堆维护 TopK

创建一个小根堆 priority_queue<P, vector<P>, cmp>,其中 Ppair<string,int>。 遍历哈希表中的每一个单词-频率对,将其加入堆中。如果加入后堆的大小超过 k ,则弹出堆顶(即当前频率最小且同频率字典序最大的单词)。这样,堆中始终保留着当前频率最高的 k 个单词,且堆顶是这 k 个单词中频率最低的(同频率下字典序最大的)。

四、提取结果

由于堆中元素顺序是频率升序、同频率字典序降序 ,而题目要求输出频率降序、同频率字典序升序 。因此,我们需要将堆中的元素依次弹出,并从结果数组的末尾开始填充 。 创建一个大小为 k 的字符串数组 ret,使用一个循环从 i = k-1 向下遍历到 0,每次取堆顶的单词存入 ret[i],然后弹出堆顶。这样,最终 ret 中就是按频率降序、同频率字典序升序排列的结果。

五、关键细节
  • 自定义比较:这是本题的核心,需确保频率和字典序的优先级方向正确,使堆能准确筛选出前 k 个高频单词。

  • 堆的大小控制:每次加入后若堆大小超过 k,立即弹出堆顶,保持堆中只有 k 个元素。

  • 结果顺序处理:由于堆弹出的顺序与所需输出顺序相反,采用逆序填充结果数组。

五、流程图