剑指Offer——最小的k个数

题目描述

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

方法一

可以利用快排中partition的思想(快排详细分析可以参考快速排序算法

  • 随机选中一个数字,在第一次partition之后,得到此时排列中该数字的位置index,位于index左边的值均小于等于该数字,位于index右边的值均大于等于该数字;
  • 当index > k-1时,最小的k个数位于index左边,则对index左边的序列进行partition;
  • 当index < k - 1时,index左边的数肯定是最小的k个数中的一部分,还有另外一部分位于index右边,所以对index右边的序列进行partition;
  • 直到index = k-1,得到最小的k个数。

这种方式的优点是时间复杂度为O(n),缺点就是需要修改输入数组。

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> res;
        if( input.empty() || k <= 0 || k > input.size() )
            return res;
        int first = 0;
        int last = input.size() - 1;
        int index = partition( input, k, first, last );
        while( index != (k-1) )
        {
            if( index > (k-1) )
            {
                last = index - 1;
                index = partition( input, k, first, last );
            }
            else
            {
                first = index + 1;
                index = partition( input, k, first, last );
            }
        }
        for( int i=0;i<k;i++ )
            res.push_back(input[i]);
        return res;
    }
 
    int partition(vector<int> &input, int k, int first, int last)
    {
        swap(input[first],input[rand()%(last-first+1)+first]);
        int v = input[first];
        int i = first+1;
        int j = last;
        while( 1 )
        {
            while( i <= last && input[i] < v )
                i++;
            while( j >= first && input[j] > v )
                j--;
            if( i >= j )
                break;
            swap(input[i],input[j]);
            i++;
            j--;
        }
        swap(input[j],input[first]);
        return j;
    }
};

方法二

可以创建一个大小为k的数据容器来存储最小的k个数字,从输入的n个整数中一个一个读入放入该容器中:

  • 如果容器中的数字少于k个,则继续向容器中放入新的值;
  • 如果容器中已有k个数字,而数组中还有值未加入,此时就不能直接插入了,需要将待插入的值与容器中的最大值进行比较,如果待插入的值大于容器中最大值,则舍弃这个待插入的值,判断下一个;反之,则将容器中最大值删除,将待插入的值放入容器中。直到数组中的所有数都判断完毕,容器中的数就是最小的k个数。

对于这个容器的实现,我们可以使用最大堆的数据结构,最大堆中,根节点的值大于它的子树中的任意节点值。自己实现最大堆比较复杂,C++中,可以使用优先队列来完成这个工作,优先队列中,每次top()、pop()取出的是队列中的最大值。

方法的时间复杂度为O(nlogn)。

优点: 

1. 不会改变原来数组; 
2. 这种思想,适合处理海量数据,特别是n大k小的情况。在处理海量数据的时候,受内存限制,数据可能不能一次全部读入内存,此时用这种方式也很好处理,只要想每次读入一些数据,与我们的容器中最大值比较,看是否需要进行替换操作。

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> res;
        if( k <= 0 || k > input.size() )
            return res;
        priority_queue<int> q;
        for( int i=0;i<input.size();i++ )
        {
            if( i < k )
                q.push(input[i]);
            else
            {
                if( input[i] < q.top() )
                {
                    q.pop();
                    q.push(input[i]);
                }
            }
        }
        while( !q.empty() )
        {
            res.push_back(q.top());
            q.pop();
        }
        return res;
    }
};
阅读更多

没有更多推荐了,返回首页