堆主要用于解决排序和topK问题,topK问题还可以使用快速选择算法解决,其时间复杂度为o(n),但空间复杂度较高。需要时直接使用相关容器即可,只不过需要考虑使用大根堆还是小根堆。
快速选择算法(Quickselect Algorithm)是一种在未排序数组中查找第k小或第k大元素的线性时间复杂度算法。它是快速排序算法的一个变种,但与之不同的是,快速选择算法只需要对数组的一部分进行排序,而不需要对整个数组进行完全排序。以下是关于快速选择算法的详细解释:
基本思想
快速选择算法的基本思想是通过选择一个基准值(pivot),将数组分为两部分:一部分小于等于基准值,另一部分大于基准值。然后根据k与基准值的大小关系,选择其中一部分进行递归搜索,直到找到第k小或第k大元素为止。
算法步骤
数据库查询优化中的选择操作。
数据分析中的极端值查找。
竞争编程中的快速查找问题。
排降序+建小根堆:greater
struct Compare {
bool operator()(int a,int b) {
return a > b;
}
};
排升序+建大根堆:less
struct Compare {
bool operator()(int a,int b) {
return a < b;
}
};
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。
class Solution {
public:
int lastStoneWeight(vector<int>& stones) {
priority_queue<int> pq(stones.begin(),stones.end());
while(pq.size()>1){
int num1=pq.top();
pq.pop();
int num2=pq.top();
pq.pop();
if(num1!=num2){
pq.push(fabs(num1-num2));
}
}
if(pq.size()){
return pq.top();
}
return 0;
}
};
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。
请实现 KthLargest 类:
KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
class KthLargest {
public:
KthLargest(int k, vector<int>& nums) {
s=k;
int i=0;
for(i=0;i<nums.size()&&k--;++i){
pq.push(nums[i]);
}
for(;i<nums.size();++i){
if(nums[i]>pq.top()){
pq.pop();
pq.push(nums[i]);
}
}
}
int add(int val) {
pq.push(val);
if(pq.size()>s){
pq.pop();
}
return pq.top();
}
priority_queue<int,vector<int>,greater<int>> pq;
int s;
};
/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest* obj = new KthLargest(k, nums);
* int param_1 = obj->add(val);
*/
给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。
class Solution {
public:
class Compare {
public:
bool operator()(const pair<string, int>& p1,const pair<string, int>& p2) const {
if (p1.second == p2.second) {
return p1.first < p2.first;
}
return p1.second > p2.second;
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
map<string, int> dir;
int i = 0;
while (i < words.size()) {
dir[words[i++]]++;
}
vector<string> vs(k,string());
priority_queue<pair<string, int>,vector<pair<string, int>>,Compare> pq;
for(auto& e:dir){
pq.push(e);
if(pq.size()>k){
pq.pop();
}
}
while(k--){
vs[k]=pq.top().first;
pq.pop();
}
return vs;
}
};
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
例如 arr = [2,3,4] 的中位数是 3 。
例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。
实现 MedianFinder 类:
MedianFinder() 初始化 MedianFinder 对象。
void addNum(int num) 将数据流中的整数 num 添加到数据结构中。
double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。
用大根堆left维护已有数据的前m个数,小根堆right维护已有数据的后n个数,其中mn或者m=n+1:
如果mn:中位数为 (left.top()+right.top())/2.0
如果m==n+1:中位数为 left.top()
class MedianFinder {
public:
MedianFinder() {}
void addNum(int num) {
if(left.size()==right.size()){
if(0==left.size()){
left.push(num);
}else{
if(num<=right.top()){
left.push(num);
}else{
left.push(right.top());
right.pop();
right.push(num);
}
}
}else{
if(num<left.top()){
right.push(left.top());
left.pop();
left.push(num);
}else{
right.push(num);
}
}
}
double findMedian() {
if(left.size()==right.size()){
return (left.top()+right.top())/2.0;
}
return left.top();
}
priority_queue<int,vector<int>,less<int>> left;
priority_queue<int,vector<int>,greater<int>>right;
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/