前兩天面試的時候,面試官問了前K大的問題:先是找前K大數,其次是前K高頻數。按照面試官的思路一道一道展開~
對總體過程中思考的知識點進行一個總結:
- 爲什麼快排常用?
- 在大規模數據的時候,快速排序只會線性增長,而堆排序增加幅度很大,會遠遠大於線性。
- 堆排序指針尋址會耗費很多時間,但是快速排序的話只是移動到前後位置。
- 參考博文爲什麼快排最好
- 介紹一下C++構造函數?
- 構造函數沒有返回值
- 構造函數用作初始化
- 構造函數可以重載
- 點操作符和箭頭操作符的區別?
- 點操作符的對象是實體
- 箭頭操作符的對象是指針
- C++ 優先隊列 堆的使用方式?如何自定義?
- 參考博文自定義堆
- 爲什麼sort函數自定義cmp,在類裏必須加static關鍵字?
- sort中的比較函數compare要聲明爲靜態成員函數或全局函數,不能作爲普通成員函數。非靜態成員函數是依賴於具體對象的,而std::sort這類函數是全局的,無法再sort中調用非靜態成員函數。靜態成員函數或者全局函數是不依賴於具體對象的, 可以獨立訪問,無須創建任何對象實例就可以訪問。同時靜態成員函數不可以調用類的非靜態成員。參考博文
目錄
1. Leetcode 215. 數組中的第K個最大元素
- 題目描述
在未排序的數組中找到第 k 個最大的元素。請注意,你需要找的是數組排序後的第 k 個最大的元素,而不是第 k 個不同的元素。
示例 1:
輸入: [3,2,1,5,6,4] 和 k = 2
輸出: 5
示例 2:輸入: [3,2,3,1,2,4,5,5,6] 和 k = 4
輸出: 4
說明:你可以假設 k 總是有效的,且 1 ≤ k ≤ 數組的長度。
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
- 思路分析
這個問題有一種非常常見的思路,就是排序,然後取第K個元素即可。最好的時間複雜度是nlogn。但是這個思路,顯然不能讓面試官滿意。說到排序,我們常用的就是快速排序。排序排序有一個partion函數,設定一個哨兵,每次把一個數組分成兩部分,前部分小於哨兵元素(或者大於),後部分大於哨兵元素(或者小於)。這樣每次調用一次partion函數就可以將一個元素放在其應該在的位置上。我們要求取的是第K大的元素,我們並不關心前k-1個元素是否有序。所以我們可以採用partion函數來尋找第K大的元素。按照通常的解法,我們選取第一個元素作爲哨兵。經過一輪partion,如果當前元素的下標小於k-1,表示我們要找的第K大的元素在當前元素的後半部分,否則反之。這樣我們最多調用K次partion就可以找到第K大的元素,時間複雜度爲Klogn.
- 代碼設計
class Solution {
public:
void partion(vector<int>&nums,int left,int right,int k)
{
if(left<right)
{
int privot=nums[left];
int i=left;
int j=right;
while(i!=j)
{
while(i<j&&nums[j]<=privot)
{
--j;
}
if(i<j)
{
nums[i++]=nums[j];//nums[i]原本存在是privot 已經保存了
}
while(i<j&&nums[i]>privot)
{
++i;
}
if(i<j)
{
nums[j--]=nums[i];
}
}
//cout<<"p:"<<privot<<endl;
nums[i]=privot;
if(i<k-1)
partion(nums,i+1,right,k);
else if(i>k-1)
partion(nums,left,i-1,k);
else
return;
}
}
int findKthLargest(vector<int>& nums, int k) {
int n=nums.size();
if(n<=0||n<k)
return 0;
//不關心數組是否有序 只關心是第k個
partion(nums,0,n-1,k);
return nums[k-1];
}
};
這時候,面試官接着問,如果是數據流呢?你事先並不知道有多少個元素?這時候,我們只知道要求前K大。
2. Leetcode 703. 數據流中的第K大元素
-
題目描述
設計一個找到數據流中第K大元素的類(class)。注意是排序後的第K大元素,不是第K個不同的元素。
你的 KthLargest 類需要一個同時接收整數 k 和整數數組nums 的構造器,它包含數據流中的初始元素。每次調用 KthLargest.add,返回當前數據流中第K大的元素。
示例:
int k = 3;
int[] arr = [4,5,8,2];
KthLargest kthLargest = new KthLargest(3, arr);
kthLargest.add(3); // returns 4
kthLargest.add(5); // returns 5
kthLargest.add(10); // returns 5
kthLargest.add(9); // returns 8
kthLargest.add(4); // returns 8
說明:
你可以假設 nums 的長度≥ k-1 且k ≥ 1。來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/kth-largest-element-in-a-stream
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
- 思路分析
既然不知道有多少數,我們就想到了堆。我們只需要維護一個大小爲K的最小堆,那麼堆定元素就是第K大的元素。最小堆的堆頂元素小於堆裏的其他元素。使用STL的優先隊列構建堆。
- 代碼設計
class KthLargest {
public:
priority_queue< int ,vector< int >,greater< int > >maxheap;
int K;
KthLargest(int k, vector<int>& nums) {
int n=nums.size();
K=k;
if(n<k)
{
for(int i=0;i<n;++i)
maxheap.push(nums[i]);
}
else
{
for(int i=0;i<k;++i)
maxheap.push(nums[i]);
for(int i=k;i<n;++i)
{
if(maxheap.size()==k&&maxheap.top()<nums[i])
{
maxheap.pop();
maxheap.push(nums[i]);
}
}
}
}
int add(int val) {
if(maxheap.size()<K)
maxheap.push(val);
else
{
if(maxheap.size()==K)
{
if(maxheap.top()<val)
{
maxheap.pop();
maxheap.push(val);
}
}
}
return maxheap.top();
}
};
/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest* obj = new KthLargest(k, nums);
* int param_1 = obj->add(val);
*/
3. Leetcode 面試題 17.14. 最小K個數
- 題目描述
設計一個算法,找出數組中最小的k個數。以任意順序返回這k個數均可。
示例:
輸入: arr = [1,3,5,7,2,4,6,8], k = 4
輸出: [1,2,3,4]
提示:0 <= len(arr) <= 100000
0 <= k <= min(100000, len(arr))來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/smallest-k-lcci
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
- 代碼設計
class Solution {
public:
void partion(vector<int>&nums,int left,int right,int k)
{
if(left<right)
{
int privot=nums[left];
int i=left;
int j=right;
while(i!=j)
{
while(i<j&&nums[j]>=privot)
--j;
if(i < j)
{
nums[i++]=nums[j];
}
while(i < j&&nums[i]<privot)
{
++i;
}
if(i<j)
{
nums[j--]=nums[i];
}
}
nums[i]=privot;
if(i<k-1)
partion(nums,i+1,right,k);
else if(i>k-1)
partion(nums,left,i-1,k);
else
return;
}
}
vector<int> smallestK(vector<int>& arr, int k) {
int n=arr.size();
vector<int>res;
if(n<=0||n<k)
return res;
partion(arr,0,n-1,k);
for(int i=0;i<k;++i)
res.push_back(arr[i]);
return res;
}
};
這時候,面試官說,我們再來看一道題吧,說返回前k高頻個高頻出現的元素。
4. Leetcode 347. 前 K 個高頻元素
- 題目描述
給定一個非空的整數數組,返回其中出現頻率前 k 高的元素。
示例 1:
輸入: nums = [1,1,1,2,2,3], k = 2
輸出: [1,2]
示例 2:輸入: nums = [1], k = 1
輸出: [1]
說明:你可以假設給定的 k 總是合理的,且 1 ≤ k ≤ 數組中不相同的元素的個數。
你的算法的時間複雜度必須優於 O(n log n) , n 是數組的大小。來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/top-k-frequent-elements
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
- 思路分析
- 第一種思路:使用map存儲元素以及其出現的頻數。創建一個vector存儲map裏的元素對。根據第二個元素的大小進行進行降序排序,然後返回前K個元素即可。
- 第二種思路:使用map存儲元素以及其出現的頻數。維護一個最小堆,大小爲K,存儲的元素爲map裏的元素對。然後返回這個最小堆裏的元素即可。
- 代碼設計
class Solution {
public:
struct cmp {
bool operator()(const pair<int,int>&a,const pair<int,int>&b)
{
return a.second>b.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
map<int,int>m;
vector<int>res;
priority_queue<pair<int,int>,vector<pair<int,int>>,cmp >q;
for(int i=0;i<nums.size();++i)
{
m[nums[i]]++;
}
int map_size=m.size();//map的大小
//維護一個大小爲K的堆
map<int,int> ::iterator it;
for(it=m.begin();it!=m.end();++it)
{
if(q.size()<k)
q.push(make_pair(it->first,it->second));
else
{
if(q.size()==k&q.top().second<it->second)
{
q.pop();
q.push(make_pair(it->first,it->second));
}
}
}
while(!q.empty())
{
res.push_back(q.top().first);
q.pop();
}
return res;
}
};
class Solution {
public:
static bool cmp(const pair<int,int>&a,const pair<int,int>&b)
{
return a.second > b.second;
}
vector<int> topKFrequent(vector<int>& nums, int k) {
map<int,int>m;
vector<int>res;
for(int i=0;i<nums.size();++i)
{
m[nums[i]]++;
}
vector<pair<int,int>>v;
map<int,int>::iterator it;
for(it=m.begin();it!=m.end();++it)
{
v.push_back(make_pair(it->first,it->second));
}
sort(v.begin(),v.end(),cmp);
if(k>v.size())
k=v.size();
cout<<v.size()<<endl;
for(int i=0;i<k;++i)
{
res.push_back(v[i].first);
}
return res;
}
};