剑指offer第40题:最小的K个数

第40题:最小的K个数

题目描述:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。

示例1

输入:[4,5,1,6,2,7,3,8],4
返回值:[1,2,3,4]

实现思路

方法一:直接采用排序的方式,调用STL中sort 方法

对原数组从小到大排序后取出前 k 个数即可。

vector<int> getLeastNumbers(vector<int>& arr, int k) {
    sort(arr.begin(), arr.end());
    vector<int> v(k, 0);
    for (int i = 0; i < k; ++i) {
        v[i] = arr[i];
    }
    return v;
}

复杂度分析

  • 时间复杂度:O(n*log n),其中 n 是数组 arr 的长度。算法的时间复杂度即排序的时间复杂度。
  • 空间复杂度:O(log n),排序所需额外的空间复杂度为 O(log n)

方法二:利用快速排序的方法实现。

实现原理:基于数组的第K个数字来调整,使得比K个数字小的所有数字都位于数组的左边,比第K个数字大的所有数字都位于数组的右边,经过这样的调整后,位于数组中左边的K个数字就是最小的K个数字。

class Solution {
public:
    int partition(vector<int>& array, int low, int high)
    {
        int piv = array[low];
        while (low < high) {
            while ((low < high) && (array[high] >= piv)) {
                high--;
            }
            array[low] = array[high];

            while ((low < high) && (array[low] <= piv)) {
                low++;
            }
            array[high] = array[low];
        }
        array[low] = piv;
        return low;
    }
    
    void quickSort(vector<int>& input, int low, int high, int k)
    {
        int pivot = partition(input, low, high);
        
        if (pivot == k) {
            return;
        }
        else if (pivot > k) {
            quickSort(input, 0, pivot-1, k);
        }
        else {
            quickSort(input, pivot+1, high, k);
        }
    }
    
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k)
    {
        vector<int> ret;
        if (k == 0 || k > input.size()) {
            return ret;
        }
        int low = 0;
        int high = input.size() - 1;
        quickSort(input, low, high, k);
        for (int i=0; i<k; i++){
            ret.push_back(input[i]);
        }
        return ret;
    }
};

时间复杂度为: O ( n ) \Omicron(n) O(n) 每次partition的大小为n+n/2+n/4+... = 2n,最坏时间复杂度为 O(n^2), 因为每次partition都只减少一个元素

空间复杂度: O ( 1 ) \Omicron(1) O(1)

方法三:最大堆实现利用

实现原理:先把前 k 个数据建立一个大根堆,然后对后面的 length-k 个数依次遍历,如果当前数值小于堆顶的值时,则替换堆顶的值,然后对大根堆做向下调整。

可以采用优先级队列,C++中的优先级队列为大根堆。

class Solution {
public:
    void adjustHeap(vector<int>& input, int i, int length) 
    {
        int left = 2 * i + 1;  // i节点的左孩子,i从 0 开始
        int right = 2 * i + 2; // i节点的右孩子
        int max = i;           // 先设置父节点和子节点三个节点中最大值的位置为父节点下标
        if (left < length && input[left] > input[max]) {
            max = left;
        }
        if (right < length && input[right] > input[max]) {
            max = right;
        }
        
        //最大值不是父节点,则进行交换
        if (max != i) {
            int temp;
            temp = input[i];
            input[i] = input[max];
            input[max] = temp;
            adjustHeap(input, max, length); //递归调用,保证子树也是最大堆
        }
    }

    vector<int> GetLeastNumbers_Solution(vector<int> input, int k)
    {
        if (input.empty() || k == 0 || k > input.size()) {
            return vector<int>{};
        }

        vector<int> result;
        //建立堆
        for (int i = input.size() / 2 - 1; i >= 0; i--) {
            adjustHeap(input, i, k); //堆的大小为k
        }
        //将后面的数依次和K个数的最大值比较
        for (int i = k; i < input.size(); i++) {
            if (input[0] > input[i]) {
                int temp = input[i];
                input[i] = input[0];
                input[0] = temp;
                adjustHeap(input, 0, k);
            }
        }
        for (int i = 0; i < k; i++) {
            result.push_back(input[i]);
        }
        return result;
    }
};

时间复杂度:O(nlongk), 插入容量为k的大根堆时间复杂度为O(longk), 一共遍历n个元素
空间复杂度:O(k)

方法比较

当K数值比较小时,可采用快速排序;当原数组中的数据顺序不可修改,且K数值很大时(海量数据),采用快速排序可能会占满内存,效率不高。此时使用最大堆数据结构实现最好。

参考

(nlongk), 插入容量为k的大根堆时间复杂度为O(longk), 一共遍历n个元素
空间复杂度:O(k)

方法比较

当K数值比较小时,可采用快速排序;当原数组中的数据顺序不可修改,且K数值很大时(海量数据),采用快速排序可能会占满内存,效率不高。此时使用最大堆数据结构实现最好。

参考

LeetCode解题思路

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值