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

题目:

输入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个数,所以他是适用的。

剑指offer 面试题30—最小的k个数_第1张图片



你可能感兴趣的:(最小的k个数,剑指offer,剑指offer,面试题30,海量数据nlogk)