最小的K个数

怎甘沉沦 提交于 2020-03-10 19:38:49

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

示例2:
输入:arr = [0,1,2,1], k = 1
输出:[0]

限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000

这道题应该是比较常见的一个问题了,还有一个镜像问题是最大的K个数。总结之后,大概有如下几种解题思路:

1. 排序法

最容易想到的方法,直接按升序对原数组排序,然后输出前K个数即可。时间复杂度依赖于所使用的排序算法,快排为O(nlogn)。

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        sort(arr.rbegin(), arr.rend());
        vector<int> ans(arr.begin(), arr.begin() + k);
        return ans;
    }
};

2. 维护一个最小堆

只需要前K个最小的数,而不需要对全部数组排序,因此自然而然就可以想到建立一个最小堆,并且取K次堆顶的元素,最终就得到了最小的前K个数。建堆的时间复杂度为O(n),插入即取出堆元素后堆化的时间复杂度为O(logn),原则上这个算法的时间复杂度也是O(nlogn)(O(klogn)),但是当k比较小时,时间复杂度会想O(logn)靠近,所以相比于O(nlogn)的排序算法,效率还是会高一些。

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector<int> ans(k);
        int arrLen = arr.size();
        this->buildHeap(arr, arrLen);
        for (int i = 0; i < k; i++){
            ans[i] = arr[0];
            arr[0] = arr[--arrLen];
            heapify(arr, arrLen);
        }
        return ans;
    }

    void buildHeap(vector<int> &arr, int heapSize)
    {
        // 采用自底向上堆化
        // 建堆
        for (int i = 1; i < heapSize; i++){
            int j = i;
            while((j + 1) / 2 >= 1 && arr[j] < arr[(j + 1) / 2 - 1]){
                int temp = arr[j];
                arr[j] = arr[(j + 1) / 2 - 1];
                arr[(j + 1) / 2 - 1] = temp;
                j = (j + 1) / 2 - 1;
            }
        }
    }

    void heapify(vector<int> &arr, int heapSize)
    {
        int index = 0;
        while (1){
            int minPos = index;
            if ((index + 1) * 2 - 1 < heapSize && arr[index] > arr[(index + 1) * 2 - 1]){
                minPos = (index + 1) * 2 - 1;
            }
            if ((index + 1) * 2 < heapSize && arr[minPos] > arr[(index + 1) * 2]){
                minPos = (index + 1) * 2;
            }
            if (minPos == index){
                break;
            }
            int temp = arr[index];
            arr[index] = arr[minPos];
            arr[minPos] = temp;
            index = minPos;
        }
    }
};

3. 维护一个最大堆

思路与第二种方法类似,只不过是变成了维护一个K个元素的最大堆。将剩余的每个元素都和堆顶的元素进行比较,如果小于堆顶元素,就和堆顶元素替换,这样最终这个堆里的元素就是最小的K个数。这种方法相比于第二种方法,减少了一次堆化操作的消耗,但增大了堆化操作的次数,总的来说时间复杂度还是O(nlogn),只不过当K变化时实际的消耗会有所不同。

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        if (k == 0) return vector<int> ();
        vector<int> ans(arr.begin(), arr.begin() + k);
        this->buildHeap(ans, k);
        int arrLen = arr.size();
        for (int i = k; i < arrLen; i++){
            if (arr[i] < ans[0]){
                ans[0] = arr[i];
                heapify(ans, k);
            }
        }
        return ans;
    }

    void buildHeap(vector<int> &arr, int heapSize)
    {
        // 采用自底向上堆化
        // 建堆
        for (int i = 1; i < heapSize; i++){
            int j = i;
            while((j + 1) / 2 >= 1 && arr[j] > arr[(j + 1) / 2 - 1]){
                int temp = arr[j];
                arr[j] = arr[(j + 1) / 2 - 1];
                arr[(j + 1) / 2 - 1] = temp;
                j = (j + 1) / 2 - 1;
            }
        }
    }

    void heapify(vector<int> &arr, int heapSize)
    {
        int index = 0;
        while (1){
            int maxPos = index;
            if ((index + 1) * 2 - 1 < heapSize && arr[index] < arr[(index + 1) * 2 - 1]){
                maxPos = (index + 1) * 2 - 1;
            }
            if ((index + 1) * 2 < heapSize && arr[maxPos] < arr[(index + 1) * 2]){
                maxPos = (index + 1) * 2;
            }
            if (maxPos == index){
                break;
            }
            int temp = arr[index];
            arr[index] = arr[maxPos];
            arr[maxPos] = temp;
            index = maxPos;
        }
    }
};

4. 基于快速排序的partition

今天看面经看到这个问题,面试官问除了堆之外有没有可以达到O(n)的算法,问的就是基于快速排序的这个操作。

待补
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!