leetcode 347. Top K Frequent Elements(优先队列的使用)

算法思路:
首先,将每个数出现的频率以键值对的方式存入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]
点赞