面试题40:最小的K个数

题目描述

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

方法一:使用C++标准库sort,时间复杂度o(nlogn)

//方法一:使用C++标准库sort,时间复杂度o(nlogn)
class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
         
        vector<int> tmp;
        if(input.empty() || k == 0 || k > input.size()) return tmp;
        sort(input.begin(), input.end());
        tmp.assign(input.begin(), input.begin() + k);
        return tmp;
    }
};
class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        if (input.empty() || !k || k > input.size()) {
            return {};
        }
        sort(input.begin(), input.end(), less<int>());
        input.resize(k); // 将a的现有元素个数调至k个,多则删,少则补,其值随机
        return input;
    }
};

方法二:快排,时间复杂度o(n)

基于快速排序的划分思想,使用partition对数组进行划分;如果基于数组的第k个数字来划分,使得比第k个数字小的所有数字都位于数组的左边,比k个数字大的所有数字都位于数组的右边。这样调整之后,位于数组中的左边的k个数字就是最小的k个数字

#if 1
//方法二:快排
class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k){
        vector<int> output;
        int length = input.size();
        if(input.empty() || k <= 0 || k > length || length <= 0)
            return output;
        if(length == k)
            return input;

        int start = 0, end = length - 1;
        int index = partition(input, start, end);

        while(index != k - 1 ){
            if(index < k -1) 
                    start = index+1;
                else 
                    end = index-1;
            index = partition(input,start,end);
        } 

        output.assign(input.begin(), input.begin()+k);
        return output;
    }

    int partition(vector<int>& input, int low, int high){
        int pivot = input[low];
        while(low < high){
            while(low < high && pivot <= input[high])
                --high;
            input[low] = input[high];
            while(low < high && pivot >= input[low])
                ++low;
            input[high] = input[low];
        }
        input[low] = pivot;
        return low;
    }
};

#elif 0
//对整个序列进行快排
class Solution {
public:
    int Partition(vector<int>& v, int low, int high){
        int pivot = v[low];
        while(low < high){
            while(low < high && v[high] >= pivot) high--;
            v[low] = v[high];
            while(low < high && v[low] <= pivot) low++;
            v[high] = v[low];
        }
        v[low] = pivot;
        return low;
    }
    
    void QuickSort(vector<int>& v, int low, int high){
        if(low < high){
            int pivotpos = Partition(v, low, high);
            QuickSort(v, low, pivotpos - 1);
            QuickSort(v, pivotpos + 1, high);
        }
    }
    
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        
        vector<int> tmp;
        if(input.empty() || k == 0 || k > input.size()) 
            return tmp;
        QuickSort(input, 0, input.size() - 1);
        
        tmp.assign(input.begin(), input.begin()+k);
        return tmp;
    }
};

#endif


方法三:使用堆排序,适合处理海量数据

利用堆排序,特别适用于海量数据中寻找最大或者最小的k个数字。即构建一个大堆容器,初始化大小为k,变量初始数,如初始数组大小小于等于k直接返回,如果大于k,则选择数组的前k个元素,填充堆,然后调整为最大堆。调整完之后,继续从初始数组中拿出一个元素,如果该元素比大堆的堆顶小,则替换堆顶,继续调整为最大堆,如果大于等于堆顶则直接丢弃,不作调整。
PS:大堆还是小堆的选择很重要,不是寻找最小的k个元素就要选择小堆,而且恰恰相反。寻找最小的k个数,其实就是寻找第k个大的元素,即寻找k个数中最大的,不断调整堆,堆得元素个数是k,堆顶是最大值,遍历完初始数组后,堆中存在的元素即使我们所要寻找的k个最小元素。

最大的K个数---- 建小堆
最小的K个数----建大堆

堆排序(手写堆)

//方法三:堆
//用最大堆保存这k个数,每次只和堆顶比,如果比堆顶小,删除堆顶,新数入堆。
#if 1
class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> result;
        if(input.size() == 0 || k==0 || k > input.size())
            return result;
        
        构建最大堆
       for(int i = (k >> 1- 1 ;i >= 0;i--)//初始化堆    //将初始化堆调整成大根堆
            adjustHeap(input,i,k);    //堆的元素个数为k
 
        //从第k个元素开始分别与最大堆的最大值做比较,如果比最大值小,则替换并调整堆。
        //最终堆里的就是最小的K个数。
        for (int i = k; i <input.size() ; i++) {
            if(input[0]>input[i]){
                input[0] = input[i];
                //swap(input[0],input[i]);
                adjustHeap(input,0,k);    
            }
        }
        result.assign(input.begin(), input.begin() + k);
        return result;
    }
     
#if 0
    void adjustHeap(vector<int>&input,int i,int length){ //堆调整
        int child=i*2+1;
        if(child <= length -1){    //有左孩子
            if(child < length-1 && input[child+1]>input[child])    //有右孩子,且有孩子比左孩子大
                child=child+1;
            if(input[child]>input[i]){
                swap(input[child],input[i]);
                adjustHeap(input,child,length);
            }
        }
    }

#elif 1    //根节点序号为0开始的堆排序
    void adjustHeap(vector<int>&input,int k,int len){
        int temp = input[k];
        for(int i = 2*k + 1; i <= len -1; i = i * 2 + 1){    //for(int i = 2*k + 1; i <= len -1; i * 2 + 1)为错误语法
            if(i < len -1 && input[i] < input[i+1])
                ++i;
            if(temp > input[i])
                break;
            else{
                input[k] = input[i];
                k = i;
            }
        }
        input[k] = temp;
}
#endif
};
#endif

/*
//堆排序流程
    void Heapsort(vector<int> &input, int length){
        for(int i = length/2 - 1; i >= 0; i--){
            adjustHeap(input, i, length);    //初始化堆
        }
        for(int i = length-1; i >= 0; i--){
            swap(input[i], input[0]);
            adjustHeap(input, 0, i);    // 重新调整堆
        }
    }
};
*/

20200910

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector<int> res;
        int len = arr.size();
        if (arr.empty() || k <=0 || k > len) 
            return res;
        
        for (int i = (len/2 - 1);i >= 0; --i) 
            AdjustDown(arr,k,i);  //初始化元素个数为k个的最大堆

        for (int i = k; i < len; ++i) {
            if (arr[0] > arr[i]) {
                arr[0] = arr[i];
                AdjustDown(arr,k,0);
            }
        }
        res.assign(arr.begin(),arr.begin()+k);
        return res;
    }

    void AdjustDown(vector<int> &A, int len, int k )  //k当前待调整的节点
    {
         int temp = A[k];
         for (int i = 2*k + 1; i <= len - 1; i = 2*i + 1) {
             if (i < len - 1 && A[i] < A[i + 1]) 
                ++i;
             if (temp < A[i]) {
                 A[k] = A[i];
                 k = i;
             }
             else 
                break;
         }
         A[k] = temp;
    }
};

堆排序(STL)

也可以直接使用STL实现中的堆排序,STL中并没有实现堆这种数据结构,但是algorithm中实现了堆排序算法。主要就是四个函数,make_heap(),pop_heap(),push_heap(),sort_heap()。

  • make_heap()
make_heap(first ,last)
make_heap(first ,last, cmpObject)

将[ first, last )范围进行堆排序,默认使用less < int>, 即 :最大元素放在第一个(默认为大根堆)。

  • pop_heap()
pop_heap(first ,last)
pop_heap(first ,last, cmpObject)

将front(即第一个最大元素)移动到end的前部,同时将剩下的元素重新构造成(堆排序)一个新的heap

  • push_heap()
push_heap(first ,last)
push_heap(first ,last, cmpObject)

对刚插入的尾部元素做堆排序。

  • sort_heap()
sort_heap(first ,last)
sort_heap(first ,last, cmpObject)

将一个堆做排序,最终成为一个有序的系列,可以看到sort_heap时,必须先是一个堆(两个特性:1、最大元素在第一个 2、添加或者删除元素以对数时间),因此必须先做一次make_heap.

//20200308,堆STL
class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> res;
        if(input.empty() || input.size() < k || k <= 0) 
            return res;
        //初始化vector
        res.assign(input.begin(), input.begin() + k);
        //for(int i = 0; i < k; ++i){
        //    res.push_back(input[i]);
        //}
        
        //建堆
        make_heap(res.begin(), res.end());    //第三个参数默认为less<>(),即大根堆
        
        for(int i = k; i < input.size(); ++i){
            if(input[i] < res[0]){
                // res[0]出堆,然后再删除
                pop_heap(res.begin(), res.end());    //第三个参数默认为less<>(),即大根堆
                res.pop_back();
                //input[i]入堆
                res.push_back(input[i]);
                push_heap(res.begin(), res.end());
            }
        }
        return res;
    }
};

堆排序(priority_queue)

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        int len = arr.size();
        vector<int > res(k,0); //vector<int> res;则无法通过编译?
        if (arr.empty() || k <= 0 || k > len)
            return res;
        priority_queue<int> Q;
        for (int i = 0; i < k; ++i)  Q.push(arr[i]);
        for (int i = k; i < len; ++i) {
            if (arr[i] < Q.top()) {
                Q.pop();
                Q.push(arr[i]);
            }
        }

        for (int i = 0; i < k; ++i) {
            res[i] = Q.top();//res.push_back(Q.top());上面使用vector<int>res;则可以通过编译
            Q.pop();
        }
        return res;
    }
};

通过assign进行赋值时,目标vector之前可为空,如果通过元素一个个赋值,则应该先初始化目标vector,或通过resize申请空间,或者使用push_back压入元素

方法二和方法三优缺点:比较

思路2
优点:节省空降,时间复杂度平均为O(n)
缺点:需要修改原始数组
思路3
优点:不用修改原始数组,适合海量数据
缺点:时间复杂度略高O(nlogk)

参考文献

【1】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值