算法思路:
首先,将每个数出现的频率以键值对的方式存入HashMap中。创建一个优先队列,队列中的元素是数字和该数字出现的频率组合的信息对,为此,我特地创建了一个信息对的类(C++提供了,java貌似没有提供),不过也挺简单的,当是学习吧。需要重写比较器,使得进队后信息对是按频率从小到大排序(底层是一个最小堆)。然后利用迭代器遍历整个HashMap,当队列长度等于k时,我们需要维护这个优先队列使其的长度保持k,当队列长度小于k,执行入队操作。遍历完map后,得到一个优先队列,该队列存储了前k大频率的信息对。将其出队,将信息对的第一个元素存入list中后返回。
因为对堆的一次调序时间复杂度为O(logn),我们需要维护一个k长度的优先队列(其底层是一个最小堆),时间复杂度为O(logk),对HashMap遍历为O(n),总的时间复杂度为O(nlogk)。空间复杂度为O(n)。参考代码:
class Pair<E extends Object,F extends Object>{
private E first;
private F second;
public Pair(E x,F y){
this.first = x;
this.second = y;
}
public E getFirst(){
return first;
}
public F getSecond(){
return second;
}
public void setFirst(E first){
this.first = first;
}
public void setSecond(F second){
this.second = second;
}
}
//leetcode 347. Top K Frequent Elements
public static List<Integer> topKFrequent(int[] nums, int k) {
List<Integer> ans = new ArrayList<Integer>();
HashMap<Integer, Integer> map = new HashMap<Integer,Integer>();
for(int i=0; i<nums.length; i++){
map.put(nums[i], map.getOrDefault(nums[i], 0)+1);
}
Queue<Pair<Integer,Integer>> queue = new PriorityQueue<Pair<Integer,Integer>>(k,comparator);
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<Integer, Integer> entry = iterator.next();
if(queue.size()==k){
if(entry.getValue()>queue.peek().getSecond()){
queue.poll();
queue.add(new Pair<Integer, Integer>(entry.getKey(),entry.getValue()));
}
}else{
queue.add(new Pair<Integer, Integer>(entry.getKey(),entry.getValue()));
}
}
while(!queue.isEmpty()){
ans.add(queue.poll().getFirst());
}
return ans;
}
public static Comparator<Pair> comparator = new Comparator<Pair>() {
@Override
public int compare(Pair o1, Pair o2) {
// TODO Auto-generated method stub
int a = (int) o1.getSecond();
int b = (int) o2.getSecond();
return a-b;
}
};
优化思路:
上述算法的时间复杂度为O(nlogk),能否优化为O(nlog(n-k)),答案是可以的。我们要维护的是一个n-k个元素的优先队列(底层用最大堆实现,需要重写比较器),当n-k为0时,处理后直接返回。遍历Map的过程和上述差不多,不再赘述。在leetcode提交时,该算法要比上一个算法快1ms。
贴代码作为参考:
//leetcode 347. Top K Frequent Elements
public static List<Integer> topKFrequent2(int[] nums, int k) {
List<Integer> ans = new ArrayList<Integer>();
HashMap<Integer, Integer> map = new HashMap<Integer,Integer>();
for(int i=0; i<nums.length; i++){
map.put(nums[i], map.getOrDefault(nums[i], 0)+1);
}
int n = map.size()-k;
if(n==0){
for(Integer key:map.keySet()){
ans.add(key);
}
return ans;
}
Queue<Pair<Integer,Integer>> queue = new PriorityQueue<Pair<Integer,Integer>>(n,comparator);
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<Integer, Integer> entry = iterator.next();
if(queue.size()==n){
if(entry.getValue()<queue.peek().getSecond()){
ans.add(queue.poll().getFirst());
queue.add(new Pair<Integer, Integer>(entry.getKey(),entry.getValue()));
}else{
ans.add(entry.getKey());
}
}else{
queue.add(new Pair<Integer, Integer>(entry.getKey(),entry.getValue()));
}
}
return ans;
}
public static Comparator<Pair> comparator = new Comparator<Pair>() {
@Override
public int compare(Pair o1, Pair o2) {
// TODO Auto-generated method stub
int a = (int) o1.getSecond();
int b = (int) o2.getSecond();
return b-a;
}
};
总结:java中的优先队列,默认情况下是最小元素优先出队(底层是最小堆)。在自定义的比较器中,通过做实验发现,若返回的是第一个元素减第二个元素的值(int型),则是一个最小元素优先队列,反之,是一个最大元素优先队列。上述的两个例子可以很好地证明,然而,我又做了一个简单的实验。附上代码:
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] nums = {1,1,1,2,2,3};
Queue<Integer> queue = new PriorityQueue<Integer>(comparator2);
for(int i=0;i<nums.length;i++)
queue.add(nums[i]);
System.out.println(queue);
Queue<Integer> queue2 = new PriorityQueue<Integer>(comparator1);
for(int i=0;i<nums.length;i++)
queue2.add(nums[i]);
System.out.println(queue2);
}
public static Comparator<Integer> comparator2 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// TODO Auto-generated method stub
return o2-o1;
}
};
public static Comparator<Integer> comparator1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// TODO Auto-generated method stub
return o1-o2;
}
};
运行结果如下:
[3, 2, 2, 1, 1, 1]
[1, 1, 1, 2, 2, 3]