给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。
示例 1:
输入: words = [“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 大。所以构建一个 大小为 K 的小根堆按照上述规则自定义排序的比较器。然后依次将单词加入堆中,当堆中的单词个数超过 K 个后,我们需要弹出顶部最小的元素使得堆中始终保留 K 个元素,遍历完成后剩余的 K 个元素就是前 K 大的。最后我们依次弹出堆中的 K 个元素加入到所求的结果集合中。
注意:因为构建的是小根堆,所以从顶部弹出的元素顺序是从小到大的,所以最后我们还需要反转集和。
public class Solution {public List<String> topKFrequent(String[] words, int k) {// 1.初始化 哈希表 key -> 字符串 value -> 出现的次数。Map<String, Integer> count = new HashMap<>();for (String word : words) {count.put(word, count.getOrDefault(word, 0) + 1);}// 2.用 list 存储字符 key 然后自定义 Comparator 比较器对 value 进行排序。List<String> candidates = new ArrayList<>(count.keySet());// 此处为使用 lambda 写法candidates.sort((a, b) -> {// 字符串频率相等按照字典序比较使得大的在堆顶,Java 可以直接使用 compareTo 方法即可。if (count.get(a).equals(count.get(b))) {return a.compareTo(b);} else {// 字符串频率不等则按照频率排列。return count.get(b) - count.get(a);}});// 3.截取前 K 大个高频单词返回结果。return candidates.subList(0, k); }
}
排序关键处不使用 lambda 写法如下:
PriorityQueue<String> minHeap = new PriorityQueue<>(new Comparator<String>() {@Overridepublic int compare(String s1, String s2) {if (count.get(s1).equals(count.get(s2))) {return s2.compareTo(s1);} else {return count.get(s1) - count.get(s2);}}
});
复杂度分析
- 时间复杂度:O(Nlog(K))。其中 N 是 words 数组的长度。起初我们用 O(N) 的时间计算每个单词的频率,然后将 N 个单词添加到堆中(堆的大小最大为 K ),添加每个单词的时间为 O(log(K))。总共消耗 O(Nlog(K))。弹出 K 次我们忽略不计,所以总共是 O(Nlog(K))。
- 空间复杂度:O(N)。需要建堆和用哈希表计数。
解法二:利用哈希表排序然后返回前 KKK 个元素
我们用哈希表存储所有单词出现的次数,key 为单词 value 为频率次数。然后根据哈希表中的 value 进行排序,排序的规则还是上文所分析的,下面给出代码。
public class Solution {public List<String> topKFrequent(String[] words, int k) {// 1.初始化 哈希表 key -> 字符串 value -> 出现的次数。Map<String, Integer> count = new HashMap<>();for (String word : words) {count.put(word, count.getOrDefault(word, 0) + 1);}// 2.用 list 存储字符 key 然后自定义 Comparator 比较器对 value 进行排序。List<String> candidates = new ArrayList<>(count.keySet());// 此处为使用 lambda 写法candidates.sort((a, b) -> {// 字符串频率相等按照字典序比较使得大的在堆顶,Java 可以直接使用 compareTo 方法即可。if (count.get(a).equals(count.get(b))) {return a.compareTo(b);} else {// 字符串频率不等则按照频率排列。return count.get(b) - count.get(a);}});// 3.截取前 K 大个高频单词返回结果。return candidates.subList(0, k); }
}
排序关键处不使用 lambda 写法如下:
candidates.sort(new Comparator<String>() {@Overridepublic int compare(String a, String b) {if (count.get(a).equals(count.get(b))) {return a.compareTo(b);} else {return count.get(b) - count.get(a);}}
});
复杂度分析:
-
时间复杂度:O(Nlog(N))。其中 N 是 words 的长度。需要 O(N) 时间计算每个单词的频率,然后集合排序需要 O(Nlog(N))。
-
空间复杂度:O(N)。使用哈希表来存储单词的空间。