面試題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進行比較。