题目描述
给一非空的单词列表,返回前 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 次。
方法一:哈希表+排序
思路
这一题的思路是先用哈希表算出列表中每个单词(字符串类型)出现的频数,然后对单词进行排序,如果两个单词的频数相同,那么让字母顺序小的单词排在前面,如果两个单词的频数不同,那么让频数大的排在前面
Java代码
class Solution {
public List<String> topKFrequent(String[] words, int k) {
Map<String, Integer> map = new HashMap<>();
for(String word:words) { // 通过哈希表,保存每个单词出现的频数
map.put(word, map.getOrDefault(word, 0)+1);
}
List<String> res = new ArrayList<>(); // 定义一个由去重后的单词组成的List
for(Map.Entry<String, Integer> entry : map.entrySet()) {
res.add(entry.getKey());
}
Collections.sort(res, new Comparator<String>() {
public int compare(String s1, String s2) {
if (map.get(s1) == map.get(s2)) { // 如果两个单词的频数相同,那么让字母顺序小的单词排在前面
return s1.compareTo(s2);
} else { // 如果两个单词的频数不同,那么让频数大的排在前面
return map.get(s2) - map.get(s1);
}
}
});
return res.subList(0, k); // 返回前 k 个出现次数最多的单词
}
}
时间复杂度:O(nlogk)。使用哈希表统计词频,复杂度为 O(n);排序的时间复杂度为O(nlogn)
空间复杂度:O(n)
方法二:哈希表+优先级队列(小根堆)
思路
通常在问题中看到 前 K 大,前 K 小 或者 第 K 个, K 个最 等等类似字样,一般情况下我们都可以用堆来做。先用哈希表算出列表中每个单词(字符串类型)出现的频数,然后因为题目要求前 K 大,所以构建一个 大小为 K 的小根堆按照上述规则自定义排序的比较器。然后依次将单词加入堆中(通过优先级队列实现),当堆中的单词个数超过 K 个后,我们需要弹出顶部最小的元素(顶部最小的元素是指当前频数最小的单词)使得堆中始终保留 K 个元素,遍历完成后剩余的 K 个元素就是前 K 大的。最后我们依次弹出堆中的 K 个元素加入到所求的结果集合中。由于此时结果集合中的元素是按照频数从低到高排序的,所以最后需要对集合进行翻转。
Java代码
class Solution {
public List<String> topKFrequent(String[] words, int k) {
Map<String, Integer> map = new HashMap<>();
for(String word:words) { // 通过哈希表,保存每个单词出现的频数
map.put(word, map.getOrDefault(word, 0)+1);
}
PriorityQueue<String> pq = new PriorityQueue<String>(new Comparator<String>() {
public int compare(String s1, String s2) { // 重点!!!注意,跟上面哈希表+排列相比,此处的compare()刚好相反,因为我们使用的是小根堆,每次弹出的顶部元素是频数最小的元素,这样最终保存下来的就是频数大的元素
if(map.get(s1) == map.get(s2)) {
return s2.compareTo(s1);
} else {
return map.get(s1) - map.get(s2);
}
}
});
for(String key : map.keySet()) { // 依次将单词加入堆中(通过优先级队列实现)
pq.offer(key);
if(pq.size() > k) { // 当堆中的单词个数超过 K 个后,我们需要弹出顶部最小的元素(顶部最小的元素是指当前频数最小的单词)使得堆中始终保留 K 个元素,遍历完成后剩余的 K 个元素就是频数前 K 大的
pq.poll();
}
}
List<String> res = new ArrayList<>();
while(pq.size() > 0) {
String cur = pq.poll();
res.add(cur);
} // 注意此时结果集合中的单词是按照频数从低到高排列的,所以后面还需要加上一步翻转
Collections.reverse(res);
return res;
}
}
复杂度分析
时间复杂度:O(nlogk)使用哈希表统计词频,复杂度为 O(n);使用最多 n 个元素维护一个大小为 k 的堆,复杂度为 O(nlogk);输出答 案复杂度为 O(k)(同时 k≤n)。整体复杂度为 O(nlogk)
空间复杂度:O(n)