查找數組中最小的k個數 | 最小的k個數

面試題30:最小的k個數

1.題目描述

輸入n個整數,找出其中最小的k個數。例如輸入4、5、1、6、2,7、3、8這8個數字,則最小的4個數字是1、2、3、4。

2.題目分析

解法一:O(n)的算法,只有當我們可以修改輸入的數組時可用

從解決面試題29“數組中出現次數超過一半的數字”得到了啓發,我們同樣可以基於 Partition函數來解決這個問題。如果基於數組的第k個數字來調整,使得比第k個數字小的所有數字都位於數組的左邊,比第k個數字大的所有數字都位於數組的右邊。這樣調整之後,位於數組中左邊的k個數字就是最小的k個數字(這k個數字不一定是排序的)。下面是基於這種思路的參考代碼:

void GetLeastNumbers(int* input,int n,int* output,int k)//n爲input數組的長度
{
    if(input == NULL || output == NULL || k>n || n<=0 || k<=0)    return;
    
    int start = 0;
    int end = n-1;
    int index = Partition(input,n,start,end);
    while(index != k-1)
    {
        if(index>k-1)
        {
            end = index - 1;
            index = Partition(input,n,start,end);
        }
        else
        {
            start = index + 1;
            index = Partition(input,n,start,end);
        }
    }

    for(int i= 0;i<k;++i)
    {
        output[i] = input[i];//此時output數組中存放的就是前k個小的數字
    }
}

解法二:O(nlog k)的算法,特別適合處理海量數據

我們可以先創建一個大小爲k的數據容器來存儲最小的k個數字,接下來我們每次從輸入的n個整數中讀入一個數。如果容器中已有的數字少於k個,則直接把這次讀入的整數放入容器之中;如果容器中已有k個數字了,也就是容器已滿,此時我們不能再插入新的數字而只能替換已有的數字。找出這已有的k個數中的最大值,然後拿這次待插入的整數和最大值進行比較。如果待插入的值比當前已有的最大值小,則用這個數替換當前已有的最大值;如果待插入的值比當前已有的最大值還要大,那麼這個數不可能是最小的k個整數之一,於是我們可以拋棄這個整數。

因此當容器滿了之後,我們要做3件事情:

  1. 是在k個整數中找到最大數;
  2. 是有可能在這個容器中刪除最大數;
  3. 是有可能要插入一個新的數字。

如果用一個二叉樹來實現這個數據容器,那麼我們能在O(log k)時間內實現這三步操作。因此對於n個輸入數字而言,總的時間效率就是O(nlogk)。

我們可以選擇用不同的二叉樹來實現這個數據容器。由於每次都需要找到k個整數中的最大數字,我們很容易想到用最大堆。在最大堆中,根結點的值總是大於它的子樹中任意結點的值。於是我們每次可以在O(1)得到已有的k個數字中的最大值,但需要O(logk)時間完成刪除及插入操作。我們自己從頭實現一個最大堆需要一定的代碼,這在面試短短的幾十分鐘內很難完成。我們還可以採用紅黑樹來實現我們的容器。紅黑樹通過把結點分爲紅、黑兩種顏色並根據一些規則確保樹在一定程度上是平衡的,從而保證在紅黑樹中查找、刪除和插入操作都只需要O(logk)時間。在STL中set和 multiset都是基於紅黑樹實現的。如果面試官不反對我們用STL中
的數據容器,我們就可以直接拿過來用。下面是基於STL中的 multiset的參考代碼:

typedef multiset<int,greater<int>>    inSet;//greater表示內置類型從大到小排序,less表示內置類型從小到大排序。
typedef multiset<int,greater<int>>::iterator    setIterator;//迭代器,該迭代器用來遍歷multiset

void GetLeastNumbers(const vector<int>& data,intSet&  leastNumbers,int k)
{
    leastNumbers.clear();

    if(k<1 || data.size()<k || data.empty())    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()))//因爲multiset的遍歷方式爲中序遍歷,所以begin()位置的值是最大的
            {
                leastNumbers.erase(iterGreatest);
                leastNumbers.insert(*iter);
            }
        }
    }
}

3.兩種算法的特點比較

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章