面试题30:最小的K个数及topK问题的解决

最近刷题老会被问到一些topK问题,今天把这些问题的解决方案整理一下。

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

这是《剑指offer》中的最小的K个数问题,如果不考虑数据的数量,最直观的方案就是排序然后即可找出符合要求的k个数,时间复杂度为O(Nlog(N)),这并不是一个好的解决方案。但牛客oj是可以过的。

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> output;
        if(input.empty() || k<0 || input.size()<k)
            return output;
        sort(input.begin(),input.end());
        int i=0;
        while(i<k)
            {
            output.push_back(input[i]);
            i++;
        }
        return output;
    }
};

第二种方案:我们要找的是K个数,其实并不要求把所有的数都必须排好序,只要是类有序也可以满足要求。也就是说如果当前数组是可以改变的,我们可以让第K个数左边的都是比自己小,右边是比自己大的。这样也满足条件,时间复杂度为O(N)。但就我个人而言,觉得仍不是最好的方案。数据量一旦过大,这两种方案的时间复杂度都过高。

在这里我说的第三种使用优先级队列,优先级队列是默认建小堆,然后每次只需要将堆头的数据pop掉,优先队列会自动调整保证仍是一个优先队列,接着pop,直到pop出K个数。时间复杂度为O(NlogK)。

这个题并不难,前两天在网上看到一道题类似这道题,但是又不太相同。题目如下:本公司现在要给公司员工发波福利,在员工工作时间会提供大量的水果供员工补充营养。由于水果种类比较多,但是却又不知道哪种水果比较受欢迎,然后公司就让每个员工报告了自己最爱吃的k种水果,并且告知已经将所有员工喜欢吃的水果存储于一个数组中。然后让我们统计出所有水果出现的次数,并且求出大家最喜欢吃的前k种水果。要求打印出最喜欢的水果,并且效率尽可能的高。

这道题和上面一样都是topK问题,但是这里除了统计水果的次数还要保存是哪种水果,可能看到的时候还有点懵,但是在STL中有很多数据结构供我们考虑。因为考虑到要保存两个信息,我们可以用map,map的底层是红黑树,这里我们将水果的名字和次数都保存起来。

首先通过用迭代器遍历保存数组,统计出每次水果出现的次数。接下来通过优先队列建大堆,所以这个时候需要自己去写个仿函数,将其变成大堆。然后将信息通过优先队列建大堆,这个时候只需要pop出K个数即可。代码如下。

void GetFavoriteFruit(const vector<string>& fruits, size_t k)
{
    map<string, int> count;
    for (size_t i = 0; i < fruits.size(); i++)  //把水果对应的存入map中
    {
        map<string, int>::iterator it = count.find(fruits[i]);
        if (it != count.end())
            it->second++;
        else
        {
            count.insert(make_pair(fruits[i], 1));
        }
    }


    struct Compare
    {
        bool operator()(map<string, int>::iterator l, map<string, int>::iterator r)
        {
            return l->second < r->second;
        }
    };

    priority_queue<map<string, int>::iterator,vector<map<string,int>::iterator>,Compare> p;
    map<string, int>::iterator countIt = count.begin();
    while (countIt != count.end())
    {
        p.push(countIt);
        ++countIt;  
    }

    while (k--)
    {
        p.top()->first;
        cout << p.top()->first << ":" << p.top()->second << endl;
        p.pop();
    }
}

void TestTopK()
{
    //西瓜 4  香蕉3  苹果3 橘子2
    vector<string> fruits = { "西瓜", "香蕉","西瓜", "香蕉", "苹果", "西瓜", "苹果", "橘子", "西瓜", "香蕉" };
    GetFavoriteFruit(fruits, 2);
}

优先队列头文件queue,还有就是对优先队列的接口一定要熟悉。

这里写图片描述

在上面的几种方案里,我们一直在说数据量过大怎么办,假设现在有10亿(N)个数,我们想找出前50(K)个,如果用排序去统计,那么时间复杂度会很大,这不是我们期待的。如果我们用堆去进行统计就会快速便捷很多,我们先将前50个数建小堆,然后从第50个向后比较,比堆头大就置换进来,在去调整堆,时间复杂度为:O(Nlog(K))。这种方案是比较适合处理海量数据的。

值得注意的是,堆排序只能随机访问的数据结构进行排序,也就是说数组这一类,所以在使用的时候,我是用了一个vector来保存map的迭代器,优先队列的三个参数意义分别是放什么,用什么容器放,怎么比。priority_queue< map< string, int>::iterator,vector< map< string,int>::iterator>,Compare> p,意思是优先级队列是用map的迭代器构成的,用vector保存迭代器,用自己实现的compare进行比较。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值