第K个最大的数+优化优先队列

2023-12-16 10:02:17 浏览数 (1)

第K个最大的数

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1: 输入: [3,2,1,5,6,4], k = 2 输出: 5 示例 2: 输入: [3,2,3,1,2,4,5,5,6], k = 4 输出: 4

提示: • 1 <= k <= nums.length <= 10^5 -104^ <= nums[i] <= 10^4

1.要求时间复杂度为O(n),有两种方法,一种使用优先队列,一种是基于快排的思想

2.这是我写的代码,用的优先队列,但是复杂度不是O(n),而是O(nlogn),那优先队列的时间复杂度是logn?看看源码

代码语言:javascript复制
private final static int max= 10^5  1;
//优先队列PQ
//给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
//max 为 不合规input
public static int func(int[] nums,int k) {
	if(nums==null || nums.length<k) {
		return max;
	}
	int result = max;
	PriorityQueue queue = new PriorityQueue<Integer>();

	for(int i:nums) {
		queue.add(i);
	}
	int length = queue.size();
	System.out.println("size" length);

	for(int n = 0;n<=length-k;n  ) {
		if(!queue.isEmpty()) {
			result = (int) queue.poll();
		}else {
			result = max;
		}
		System.out.println("result:" result);
	}
	return result ;
}

3.优先队列的add方法源码

1.PriorityQueue.add()
代码语言:javascript复制
public boolean add(E e){
    return offer(e)
}

public boolean offer(E e){
    if(e == null)
        throw new NullPointerException();
    modCount  ;
    int i = size;
    if(i >= queue.length)
        //扩容 ,容量翻倍
        grow(i   1);
    	size = i   1;
    if(i == 0)
        queue[0] = e;
    else
        siftUp(i,e);
    return true;
}
2.siftUp 新增
代码语言:javascript复制
private void siftUp(int k,E x){
    if(comparator != null)
        siftUpUsingComparator(k,x);
    else
        siftUpComparable(k,x);
}
3.siftUpComparable() 这里能看出来,果然复杂度是logn
代码语言:javascript复制
@SuppressWarnings("unchecked")
private void siftUpComparable(int k,E x){
    Comparable<? super E> key = (Comparable<? super E>) x;
    while(k > 0){
        int parent = (k-1)>>>1;
        Object e = queue[parent];
        if(key.compareTo((E) e)>=0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = key;
}

4.那怎么才能把时间复杂度从O(nlogn)优化成O(n)呢

我们注意到,优先队列每次新增,都会有logn的开销,所以想要降低时间复杂度,就必须要降低开销的次数,减少add操作,把减少的部分尽量换成时间复杂度为O(1)的比较操作,这样假设有m次add,那么有(n-m)次比较,综合起来就是O(klogk) O(n-k)
题目中要求第K个最大的数,数组长度是N,所以定义堆的时候大小为K,然后用剩下的N-k个数和堆顶元素比较,最后堆顶即为结果:

(1)如果K>2/N,最好做(N-K)次add操作。第K个最大的数,就是第(N-K)个最小的数,因此用(N-K)大小的最大堆,堆顶就是结果。 1——————K——N (N-K)次add

(2)如果K<2/N,最好做K次add操作。用K大小的最小堆,堆顶就是结果。 1——K——————N K次add

代码语言:javascript复制
public static int function(int[] nums,int k){
	System.out.println("step into function");
    int len = nums.length;
    int top = 0;
    int result = 0;
    if(k <= len - k){
        //最小堆
        PriorityQueue<Integer> minHeap = new PriorityQueue<>(k,(a,b) -> a-b);
        for(int i = 0;i<k;i  ){
        	minHeap.add(nums[i]);
            printHeap(minHeap);
        }
        for(int i = k;i<len;i  ){
            top = minHeap.peek();
            if(nums[i]>top){
            	minHeap.poll();
            	minHeap.add(nums[i]);
                printHeap(minHeap);
            }
        }
        result = minHeap.peek();
    }else{
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(len-k 1,(a,b) -> b-a);
        for(int i=0;i<len-k 1;i  ){
        	maxHeap.add(nums[i]);
            printHeap(maxHeap);
        }
        for(int i=len-k 1;i<len;i  ){
            top = maxHeap.peek();
            if(nums[i]<top){
            	maxHeap.poll();
            	maxHeap.add(nums[i]);
            	
            }
        }
        result = maxHeap.peek();
    }
    return result;
}

0 人点赞