🚩传送门:牛客题目
题目
给定一个字符串数组,再给定整数 ,请返回出现次数前
名的字符串和对应的次数。返回的答案应该按字符串出现频率由高到低排序。如果不同的字符串有相同出现频率,按字典序排序。对于两个字符串,大小关系取决于两个字符串从左到右第一个不同字符的 ASCII 值的大小关系。字符仅包含数字和字母 。
比如“ah1x”小于“ahb”,“231”<“32”
数据范围:字符串数满足 ,每个字符串长度
,
要求:空间复杂度 ,时间复杂度
解题思路:哈希表+小根堆
题目要求找出出现次数前 k 的字符串,最为简单的就是直接遍历数组用 Map 统计每个字符串出现的次数,接着将 Map 放入 List 再降序排序输出前 k 的字符串。但是此方法的时间复杂度 不符合题目要求,因此将 List 修改为 小根堆 。
算法过程:
- 使用哈希统计每个字符串出现的次数;
- 建立容量为 k 的小根堆,遍历 map,将统计后满足条件的字符放入最小堆中 。
条件:个数多的,个数相等放入字典排序靠前的
复杂度分析
时间复杂度: ,堆中 K 个元素的排序时间
空间复杂度: ,需要存储 n 个 Map.Entry
对于 List 比较规则是:9X 、8Y 、7A 、7B
- 数值大的在前
- 数值相等,ASCII 字典排序小的在前

// 先是按出现次数降序比较,相同则再按照字符ASCII码升序比较Collections.sort(list,(o1, o2) ->(o1.getValue().compareTo(o2.getValue()) ==0 ? o1.getKey().compareTo(o2.getKey()) :o2.getValue().compareTo(o1.getValue())));
对于 Heap 比较规则是:7B 、7A 、8Y 、9X
- 数值小的在前(小根堆,堆顶值小)
- 数值相等,ASCII 字典排序大的在前

PriorityQueue<Map.Entry<String,Integer>> queue = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {@Overridepublic int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {if (o1.getValue().equals(o2.getValue())){//字典序大的在前 所以 o2 比 o1return o2.getKey().compareTo(o1.getKey());}else {//数量小的在前所以 o1 - o2return o1.getValue() - o2.getValue();}}});
我的代码 [ 匿名内部类Comparator ]
public class Solution {public String[][] topKstrings (String[] strings, int k) {if (k == 0) return new String[][]{};String[][] res = new String[k][2];TreeMap<String,Integer> map = new TreeMap<>();// 统计各个字符串出现的次数for (int i=0;i<strings.length;++i){String s = strings[i];if (!map.containsKey(s)) {map.put(s, 1);} else {map.put(s, map.get(s) + 1);}}PriorityQueue<Map.Entry<String,Integer>> queue = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {@Overridepublic int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {if (o1.getValue().equals(o2.getValue())){return o2.getKey().compareTo(o1.getKey());}else {return o1.getValue() - o2.getValue();}}});for(Map.Entry<String,Integer> entry:map.entrySet()){// 堆的元素个数小于k,则直接加入堆中if (queue.size() < k) {queue.offer(entry);} else{if(queue.peek().getValue()<entry.getValue()||(queue.peek().getValue()==entry.getValue()&&queue.peek().getKey().compareTo(entry.getKey())>0)){//如果堆顶元素 < 新数,则删除堆顶,加入新数入堆queue.poll();queue.offer(entry);}}}// 返回topKfor (int i = k-1; i >=0; --i) {Map.Entry<String,Integer> entry =(Map.Entry)queue.poll();res[i][0] = entry.getKey();res[i][1] = String.valueOf(entry.getValue());}return res;}}
我的代码[ 外部类Comparator ]
public class Solution {//自定义能够实现升序的比较器class DescComparator implements Comparator<Map.Entry<String,Integer>>{@Overridepublic int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {if (o1.getValue().equals(o2.getValue())){return o2.getKey().compareTo(o1.getKey());}else {return o1.getValue() - o2.getValue();}}}public String[][] topKstrings (String[] strings, int k) {if (k == 0) return new String[][]{};Comparator compa = new DescComparator();String[][] res = new String[k][2];TreeMap<String,Integer> map = new TreeMap<>();// 统计各个字符串出现的次数for (int i=0;i<strings.length;++i){String s = strings[i];if (!map.containsKey(s)) {map.put(s, 1);} else {map.put(s, map.get(s) + 1);}}PriorityQueue queue = new PriorityQueue(k,compa);for(Map.Entry<String,Integer> entry:map.entrySet()){// 堆的元素个数小于k,则直接加入堆中if (queue.size() < k) {queue.offer(entry);} else if (compa.compare(queue.peek(),entry) < 0) {//如果堆顶元素 < 新数,则删除堆顶,加入新数入堆queue.poll();queue.offer(entry);}}// 返回topKfor (int i = k-1; i >=0; --i) {Map.Entry<String,Integer> entry =(Map.Entry)queue.poll();res[i][0] = entry.getKey();res[i][1] = String.valueOf(entry.getValue());}return res;}}
