详解使用「哈希表」&「优先队列」进行求前 K 个元素|Java 刷题打卡

简介: 详解使用「哈希表」&「优先队列」进行求前 K 个元素|Java 刷题打卡

题目描述



这是 LeetCode 上的 692. 前K个高频单词 ,难度为 中等


Tag : 「哈希表」、「优先队列」


给一非空的单词列表,返回前 k 个出现次数最多的单词。


返回的答案应该按单词出现频率由高到低排序。


如果不同的单词有相同出现频率,按字母顺序排序。


示例 1:


输入: ["i", "love", "leetcode", "i", "love", "coding"], k = 2
输出: ["i", "love"]
解析: "i" 和 "love" 为出现次数最多的两个单词,均为2次。
    注意,按字母顺序 "i" 在 "love" 之前。
复制代码


示例 2:


输入: ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4
输出: ["the", "is", "sunny", "day"]
解析: "the", "is", "sunny" 和 "day" 是出现次数最多的四个单词,
    出现次数依次为 4, 3, 2 和 1 次。
复制代码


注意:


  • 假定 k 总为有效值, 1 ≤ k ≤ 集合元素数。
  • 输入的单词均由小写字母组成。


扩展练习:


  • 尝试以 O(n log k) 时间复杂度和 O(n) 空间复杂度解决。


哈希表 & 优先队列(堆)



这道题是在「优先队列(堆)」裸题的基础上增加了字典序大小的比较。


相应的,我们不能只根据「词频大小」构建小根堆来获取前 kkk 个元素,还需要结合字典序大小来做。


具体的,我们可以使用「哈希表」&「优先队列」进行求解:


  1. 使用「哈希表」来统计所有的词频
  2. 构建大小为 kkk 按照「词频升序 + (词频相同)字典序倒序」的优先队列:
  • 如果词频不相等,根据词频进行升序构建,确保堆顶元素是堆中词频最小的元素
  • 如果词频相等,根据字典序大小进行倒序构建,结合 2.12.12.1 可以确保堆顶元素是堆中「词频最小 & 字典序最大」的元素
  1. 对所有元素进行遍历,尝试入堆:
  • 堆内元素不足 kkk 个:直接入堆
  • 词频大于堆顶元素:堆顶元素不可能是前 kkk 大的元素。将堆顶元素弹出,并将当前元素添加到堆中
  • 词频小于堆顶元素;当前元素不可能是前 kkk 大的元素,直接丢弃。
  • 词频等于堆顶元素:根据当前元素与堆顶元素的字典序大小决定(如果字典序大小比堆顶元素要小则入堆)
  1. 输出堆内元素,并翻转


代码:


class Solution {
    public List<String> topKFrequent(String[] ws, int k) {
        Map<String, Integer> map = new HashMap<>();
        for (String w : ws) map.put(w, map.getOrDefault(w, 0) + 1);
        PriorityQueue<Object[]> q = new PriorityQueue<>(k, (a, b)->{ 
            // 如果词频不同,根据词频升序
            int c1 = (Integer)a[0], c2 = (Integer)b[0];
            if (c1 != c2) return c1 - c2;
            // 如果词频相同,根据字典序倒序
            String s1 = (String)a[1], s2 = (String)b[1];
            return s2.compareTo(s1);
        });
        for (String s : map.keySet()) {
            int cnt = map.get(s);
            if (q.size() < k) { // 不足 k 个,直接入堆
                q.add(new Object[]{cnt, s});
            } else {
                Object[] peek = q.peek();
                if (cnt > (Integer)peek[0]) { // 词频比堆顶元素大,弹出堆顶元素,入堆
                    q.poll();
                    q.add(new Object[]{cnt, s});
                } else if (cnt == (Integer)peek[0]) { // 词频与堆顶元素相同
                    String top = (String)peek[1];
                    if (s.compareTo(top) < 0) { // 且字典序大小比堆顶元素小,弹出堆顶元素,入堆
                        q.poll();
                        q.add(new Object[]{cnt, s});
                    }
                }
            }
        }
        List<String> ans = new ArrayList<>();
        while (!q.isEmpty()) ans.add((String)q.poll()[1]);
        Collections.reverse(ans);
        return ans;
    }
}
复制代码


  • 时间复杂度:使用哈希表统计词频,复杂度为 O(n)O(n)O(n);使用最多 nnn 个元素维护一个大小为 kkk 的堆,复杂度为 O(nlog⁡k)O(n\log{k})O(nlogk);输出答案复杂度为 O(k)O(k)O(k)(同时 k≤nk \leq nkn)。整体复杂度为 O(nlog⁡k)O(n\log{k})O(nlogk)
  • 空间复杂度:O(n)O(n)O(n)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.692 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先将所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
【Java每日一题,优先队列简单题】数组修改
【Java每日一题,优先队列简单题】数组修改
|
6月前
|
存储 算法 Java
【数据结构与算法】2、链表(简单模拟 Java 中的 LinkedList 集合,反转链表面试题)
【数据结构与算法】2、链表(简单模拟 Java 中的 LinkedList 集合,反转链表面试题)
64 0
|
6月前
|
存储 Java Serverless
二叉搜索树 和 哈希表 (JAVA)
【1月更文挑战第1天】阿里云初体验 二叉搜索树 二叉搜索树的插入 二叉搜索树的查找 二叉搜索树的删除 哈希表 哈希冲突 闭散列 线性探测法 二次探测法 开散列 二叉搜索树又称二叉排序树,它具有以下性质的二叉树或空树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 它的每颗子树也分别为二叉搜索树 而哈希表是一种插入/删除/查找时间复杂度都是O(1)的数据结构,它的查询之所以也可以如此之快的的原因就是它在查找时不需要经过任何比较,直接一次从表中得到要搜索的元素。
53 0
二叉搜索树 和 哈希表 (JAVA)
|
6月前
|
存储 算法 Java
java查找算法:二分查找、哈希查找等
java查找算法:二分查找、哈希查找等
43 1
|
存储 缓存 安全
【JavaSE专栏51】Java集合类HashSet解析,基于哈希表无序非重元素集合
【JavaSE专栏51】Java集合类HashSet解析,基于哈希表无序非重元素集合
|
算法 Java
【Java】哈希表 AcWing 840. 模拟散列表
【Java】哈希表 AcWing 840. 模拟散列表
44 0
|
算法 Java
数据结构(3)基础查找算法——顺序查找、二分查找(JAVA版)
3.1.顺序查找 顺序查找,时间复杂度是O(n),逻辑很简单,就是依次遍历一个线性的数据结构判断所要查找的目标数据是否在这个数据结构里。以下是代码实现:
88 0
|
Java 容器
Java每日一练(20230510) 生成器类、螺旋矩阵II、删除链表的重复元素II
Java每日一练(20230510) 生成器类、螺旋矩阵II、删除链表的重复元素II
95 0
复杂链表的复制(剑指offer35 力扣138)java哈希表/原地拼接
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
合并两个排序的链表(剑指offer 25)Java
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。