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

题目描述

以尽可能小的代价返回某无序系列中的两个最大值,当有重复的时设置某种机制进行选择。

题解

首先要考虑的是重复的数的问题。
A.不处理重复数据方法:在处理第k大的元素时不处理重复的数据,也就是将原数组进行降序排序后,下标为k-1的元素。
B.去除重复数据方法:忽略重复的数据,这时候需要首先对a进行去重处理(可以用哈希表),然后再按A的方法求解。

先排序,再求解:

用快排或堆排序,平均时间复杂度为O(N*logN),然后取出前k个,于是总时间复杂度为O(NlogN)+O(k)=O(NlogN)。
这种做法较浪费时间,因为题目只要求找出第k大的元素,而不需要数组是有序的。

k趟冒泡:

用k趟冒泡排序选出前k大的数。

public static ArrayList<Integer> bubble(int[] arr,int k){
    ArrayList<Integer> ans = new ArrayList<>(k);//用数组返回前k大元素,最大的在最前面
    for(int i=1;i<=k;i++){
        for(int j=0;j<arr.length-i;j++){
            if(arr[j]>arr[j+1]){
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
        ans.add(arr[arr.length-i]);
    }
    return ans;
}

时间复杂度为O(N*k)。

部分快排:

在数组中随机选择枢轴mid。

如果右半边(包括a[mid])的长度恰好为k,说明a[mid]就是需要的第k大元素,直接返回a[mid]。
如果右半边(包括a[mid])的长度大于k,说明要寻找的第k大元素就在右半边,往右半边寻找。
如果右半边(包括a[mid])的长度小于k,说明要寻找的第k大元素就在左半边,往左半边寻找。
时间复杂度为O(Nlogk)。如果每次选择的枢轴都是最坏的那个,时间复杂度就会退化为O(Nk)。

借助堆:

借助一个小根堆,先建立一个规模为k的最小化堆,然后每次拿待处理的数组中元素和堆的最小元素(根结点元素值)比较。如果待插入元素大于根结点元素,则在堆中删除根结点,并把待处理的元素入堆。否则可以抛弃这个元素。

public static ArrayList<Integer> heap(int[] arr,int k){
    ArrayList<Integer> ans = new ArrayList<>(k);//用数组返回前k大元素,最小的在最前面
    Queue<Integer> priorityQueue = new PriorityQueue<>(k);
    for (int i : arr) {
        if (priorityQueue.size() < k) {//堆还未满,直接入堆
            priorityQueue.offer(i);
        }else{
            if (i >= priorityQueue.element()) {//当前元素大于堆的最小元素,当前元素入堆
                //删除堆的最小元后再入堆
                priorityQueue.poll();
                priorityQueue.offer(i);
            }
        }
    }
    while(!priorityQueue.isEmpty()){
        ans.add(priorityQueue.poll());
    }
    return ans;
}

时间复杂度为O(N*logk),因为二叉堆的插入和删除操作都是logk的时间复杂度。

posted @ 2021-04-01 09:12  HickeyZhang  阅读(382)  评论(0编辑  收藏  举报