Bootstrap

寻找第K大元素

一. 题目描述

寻找无序数组中的第k大元素

二. 思考与解法

1. 局部冒泡

针对这个问题,我们最直观的想法一般是排序。在排序后选取第k大的值。那么在运用排序算法解决这个的时候就不得不去考虑,排序算法的时间复杂度和空间复杂度。
所以针对这个问题,可以进行一些优化和思考。当K很小的时候,比如K=1。寻找最大的元素,那么一趟遍历便能找到结果。因此我们只要对冒泡排序稍做改动便可。

	public static int FindValue(int[] array, int k){
		//在第k次冒泡的时候终止
        for(int i = 0; i < k; i++){
            for(int j = 0; j < array.length-i-1; j++){
                if (array[j] > array[j+1]){
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                }
            }
        }
        return array[array.length-k];
    }

冒泡排序的算法复杂度位:O(N^2),K冒泡的算法复杂度为KN,对比排序算法的最好情况时间复杂度O(NlogN),因此当 K<logN 时K冒泡会更有优势。

2. 小顶堆

同样我们也可以借助,堆排序的方法来解决这一问题。构造大小为K的小顶堆,那可堆顶就是第K大的值。在遍历时,只需对比堆顶大的值,进行入堆和调整。最后小顶堆的顶即为所求。

	public static int FindValue(int[] array, int k){
        Queue<Integer> heap = new PriorityQueue<>(k, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1-o2;
            }
        }); //小顶堆
        for(int i = 0; i < array.length; i++){
            if(i<k){
                heap.offer(array[i]);//前k个数,直接入堆
            }else if(array[i] > heap.peek()){ //比小顶值大,则需要重新建堆
                heap.poll();
                heap.offer(array[i]);
            }
        }
        return heap.peek();
    }

3. 快排

快速排序每次至少能将一个数字放在其排序后的位置,其左边的数据是小于该数字的,右边数据是大于该数字的。没一趟快排能确定一次基准值,通过不断调整基准值,直至基准值等于N-K。此时N-K左侧的值小于它,右侧的值大于它。那么N-K对应的值便为第K大的值。

	public static int FindValue(int[] nums, int k){
        int begin=0;
        int end=nums.length-1;
        k=nums.length+1-k;
        while(begin<end){
            int pos=partition(nums,begin,end);
            if(pos==k-1) {
                break;
            } else if(pos<k-1) {
                begin=pos+1;
            } else {
                end=pos-1;
            }
        }
        return nums[k-1];
    }
    private static void swap(int[] a, int i, int j){
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
    private static int partition(int[] nums, int l, int r){
        int less=l-1;//小于区的下标
        int more=r;//大于区的下标,默认以最后一个下标的数作为划分值
        while(l<more){
            if(nums[l]<nums[r]) {
                swap(nums,++less,l++);
            } else if  (nums[l]>nums[r]) {
                swap(nums,--more,l);
            } else {
                l++;
            }
        }
        swap(nums,more,r);
        return less+1;//小于区位置+1可以得到划分的这个数的下标
    }

4. 桶排序

求出数组中的最大最小值,以1为单位间隔画桶,遍历数组中的值放入对应的桶中。最后再查找第K大的值所对应的桶。

	public static int FindValue(int[] nums, int k){
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for(int num : nums){
            max = Math.max(max, num);
            min = Math.min(min, num);
        }
        int[] bucket = new int[max-min+1];
        for(int i=0; i<nums.length; i++){
            bucket[nums[i] - min]++;
        }
        int count = 0;
        for(int j=bucket.length-1; j>=0; j--){
            count += bucket[j];
            if(count >= k) {
                return j+min;
            }
        }
        return -1;
    }

三、总结

网上类似的总结很多,这里主要参考了这篇博客:
链接: https://blog.csdn.net/orangefly0214/article/details/86527462.

;