leetcode
leetcode 601 ~ 650
前K个高频单词

前K个高频单词

难度:

标签:

题目描述

代码结果

运行时间: 24 ms, 内存: 16.1 MB


/*
思路:
1. 使用流API处理数组。
2. 首先统计每个单词的频率。
3. 然后按频率降序排列,如果频率相同则按字典序升序排列。
4. 最后返回前k个元素。
*/
import java.util.*;
import java.util.stream.*;
 
public class TopKFrequentWordsStream {
    public List<String> topKFrequent(String[] words, int k) {
        Map<String, Long> count = Arrays.stream(words)
                .collect(Collectors.groupingBy(w -> w, Collectors.counting()));
 
        return count.entrySet().stream()
                .sorted((a, b) -> {
                    if (a.getValue().equals(b.getValue())) {
                        return a.getKey().compareTo(b.getKey());
                    }
                    return b.getValue().compareTo(a.getValue());
                })
                .limit(k)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }
}
 

解释

方法:

此题解采用哈希表和最小堆(优先队列)的结合来解决问题。首先,使用一个哈希表(defaultdict)统计每个单词的出现次数。然后,定义一个自定义对象Word,它包含单词出现的次数和单词本身,重载其比较运算符以实现优先队列的正确功能。在遍历哈希表时,将每个单词以Word对象的形式添加到一个最小堆中。如果堆的大小超过k,则移除堆顶元素(出现次数最少的元素),以保持堆的大小为k。最后,从堆中取出所有元素,这些元素是按出现频率最小到最大排序的,所以需要反转以得到正确的顺序。

时间复杂度:

O(n log k)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在这个问题中选择使用最小堆来维护前k个高频单词,而不是使用最大堆或其他数据结构?
使用最小堆来维护前k个高频单词的主要原因是效率和简便性。最小堆使得我们能够快速移除堆中频率最低的单词(即堆顶元素),这样可以确保堆中始终保持最高的k个频率的单词。如果使用最大堆,则每次插入新元素后都需要检查整个堆以确定哪些元素需要被移除,这会增加算法的复杂度。而使用最小堆,只需要比较和可能移除顶部元素即可简单维护堆的大小为k,从而保证时间复杂度较低。其他数据结构如排序数组或列表虽然可以维持元素的顺序,但更新元素的成本较高,特别是在频繁插入和删除操作时效率较低。
🦆
自定义对象Word重载了比较运算符,具体是如何确保在频率相同的情况下按字典顺序排列的?能否详细解释这一比较逻辑的实现?
在Word类中重载比较运算符`__lt__`时,定义了两个主要的比较准则:首先比较单词出现的次数,如果次数相同,则按照字典顺序比较单词本身。具体实现是这样的:如果两个Word对象的`times`(出现次数)不同,则根据次数进行比较,次数少的对象视为较小。如果`times`相同,则比较两个单词的字典顺序,但由于我们需要字典顺序靠前的单词在堆中表现为较小(即更晚被移除),因此在次数相同的情况下,字典顺序靠后的单词应该返回True表示它比较小。这样可以确保在频率相同的情况下,字典顺序靠前的单词在优先队列中优先级更高。
🦆
解题思路提到最后需要将堆中的元素反转以得到正确的顺序,这是基于什么样的考虑?为什么不在插入时就维持正确的顺序?
由于使用的是最小堆,堆顶元素始终是频率最小的元素,当我们对堆元素进行pop操作时,它们是按频率从小到大的顺序被移除的。但是题目要求输出的是按频率从大到小的顺序。因此,为了获得正确的顺序,我们需要在取出所有元素后将结果列表进行反转。如果尝试在插入时就维持一个从大到小的顺序,我们需要使用最大堆,并且还要管理堆的大小限制为k,这在逻辑上更复杂且易出错。使用最小堆并在最后进行反转是一个更简单且效率高的处理方式。

相关问题

前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

 

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

 

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

 

进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

最接近原点的 K 个点

给定一个数组 points ,其中 points[i] = [xi, yi] 表示 X-Y 平面上的一个点,并且是一个整数 k ,返回离原点 (0,0) 最近的 k 个点。

这里,平面上两点之间的距离是 欧几里德距离( √(x1 - x2)2 + (y1 - y2)2 )。

你可以按 任何顺序 返回答案。除了点坐标的顺序之外,答案 确保唯一 的。

 

示例 1:

输入:points = [[1,3],[-2,2]], k = 1
输出:[[-2,2]]
解释: 
(1, 3) 和原点之间的距离为 sqrt(10),
(-2, 2) 和原点之间的距离为 sqrt(8),
由于 sqrt(8) < sqrt(10),(-2, 2) 离原点更近。
我们只需要距离原点最近的 K = 1 个点,所以答案就是 [[-2,2]]。

示例 2:

输入:points = [[3,3],[5,-1],[-2,4]], k = 2
输出:[[3,3],[-2,4]]
(答案 [[-2,4],[3,3]] 也会被接受。)

 

提示:

  • 1 <= k <= points.length <= 104
  • -104 < xi, yi < 104