剑指offer 面试题30—最小的k个数

来源:互联网 发布:淘宝联盟代码怎么用 编辑:程序博客网 时间:2024/05/14 13:45

题目:

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


基本思想:

解法一:先排序再找K:O(nlgn)

最简单的方法就是把它排序,然后找出前面的k个数字就是最小的k个数字,这种思路的时间复杂度是O(nlgn)


解法二:partition函数:O(n)

利用快速排序的partition函数来将数组分成两部分,partition函数的返回值就是排序好的数组的最终下标,其左边是比它小的数,右边是比它大的数,这样我们只需要将partition的返回值与k做比较,如果与k相等,则返回(返回的k个数字不一定是排序的);如果比k大,则最小的k个数在左边,则继续返回递归;如果比k小,则最小的k个数中有一部分在右边,则返回这部分到右边寻找。


    #include <iostream>      using namespace std;             int par(int a[],int len,int low,int high)      {            int t=a[low];           int i=low,j=high;          while(i!=j)            {                while(i<j&&a[j]>=t)  j--;              while(i<j&&a[i]<=t)  i++;              if(i<j)              {                  int temp=a[i];                  a[i]=a[j];                  a[j]=temp;              }          }          a[low]=a[i];          a[i]=t;                return i;        }            void foo(int a[],int len,int k)      {          if(len<=0)              return ;                int start=0;                int end=len-1;          int index=par(a,len,start,end);          while(index!=k-1)          {              if(index>k-1)              {                  end=index-1;                  index=par(a,len,start,end);              }              else              {                  start=index+1;                  index=par(a,len,start,end);              }          }                for(int i=0;i<k;i++)cout<<a[i]<<" ";cout<<endl;    }            int main()      {          int a[]={4,5,1,6,2,7,3,8};          int k=4;                int len = sizeof(a)/sizeof(a[0]);                foo(a,len,k);               return 0;      }  


解法三海量数据:O(nlgk)

我们可以先创建一个大小为K的数据容器来存储最小的K个数,接下来我们每次从输入的n个整数中读入一个数,如果容器中已有的数字少于K,则直接把这次读入的整数放入容器中;如果容器中已有K个数,就是容器满了,此时我们不能插入新的数字而只能替换已有的数字了。找出这K个数的最大值,然后拿这次待插入的整数和最大值相比较。如果待插入的值比当前已有的最大值小,则用这个数替换当前已有的最大值;如果待插入的值比当前已有的最大值还大,那么这个数不可能是最小的K个整数之一,于是我们可以抛弃这个数。

我们可以用二叉树(红黑树或最大堆)来实现这个数据容器。堆查找最大值O(1),删除插入为O(logk);红黑树查找、删除和插入都是O(logk)。

2种情况非常适合海量数据的处理,当n>>k的时候非常适合。

时间复杂度为:O(n*lgk)

typedef multiset<int, greater<int> >            intSet;typedef multiset<int, greater<int> >::iterator  setIterator;void GetLeastNumbers_Solution2(const vector<int>& data, intSet& leastNumbers, int k){    leastNumbers.clear();    if(k < 1 || data.size() < k)        return;    vector<int>::const_iterator iter = data.begin();    for(; iter != data.end(); ++ iter)    {        if((leastNumbers.size()) < k)            leastNumbers.insert(*iter);        else        {            setIterator iterGreatest = leastNumbers.begin();            if(*iter < *(leastNumbers.begin()))            {                leastNumbers.erase(iterGreatest);                leastNumbers.insert(*iter);            }        }    }}

解法二与解法三比较:

解法二是基于Partition其时间复杂度为O(n),比解法三要快,但是解法二是有限制的,它改变了原始数组,如果要求不能改变数组的话,就不适用解法二了。

解法三虽然慢了点,但他是有优点的,1.没有改变数组,2.使用于海量数据,如果数据量特别大,那么一次可能不能把所有数据都读入内存,那么这样的话解法二是明显不行的,因它要把所有数据都存如内存才能再做转化和比较,然而解法三就会特别适合,因为他是一个个读数据,并且容器中只存k个数,所以他是适用的。




0 0
原创粉丝点击