本篇记录笔者在算法刷题的过程中遇到的经典TopK问题
topK问题比较经典,在面试算法题中考察也比较普遍,无非就是在一群无序的数中,找到最小的K个数和最大的K个数
本篇以寻找最大的K个数为样板进行整理

面对topK问题,最能直接了当想到的做法便是排序,而后根据顺序用下标访问该元素即可
本篇侧重解法,关于快排原理本篇就不进行推导,有兴趣的小伙伴可以自行调研
代码如下:
class Solution {
public:
int findKth(vector<int> q, int n, int K) {
quick_sort(q,0,q.size()-1);
return q[n-K];
}
void quick_sort(vector<int> &q,int l,int r){
if(l>=r) return;
int x=q[l+r>>1];
int i=l-1;
int j=r+1;
while(i<j){
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j) swap(q[i],q[j]);
}
quick_sort(q, l, j);
quick_sort(q, j+1,r);
return;
}
};
平均时间复杂度:O(nlogn)
空间复杂度:O(1)
我们观察到,其实快排中我们将数组分为了两部分,其实每一轮循环中,我们要找的第K个数,没有必要对整个数组进行寻找,可以对其中一侧进行寻找,从而达到优化的效果
代码如下
class Solution {
public:
int findKth(vector<int> q, int n, int K) {
return quick_sort(q,0,q.size()-1,n-K+1);
}
int quick_sort(vector<int> &q,int l,int r,int k){
if(l>=r) return q[l];
int x=q[l+r>>1];
int i=l-1;
int j=r+1;
while(i<j){
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j) swap(q[i],q[j]);
}
if(j-l+1>=k) return quick_sort(q, l, j,k);
else return quick_sort(q, j+1,r,k-(j-l+1));
}
};
平均时间复杂度:O(n)
空间复杂度:O(1)
其实寻找到第K个数,同样可以通过维护一个堆来解决,众所周知,堆顶要么存放最大数,要么存放最小数,那么其实只要维护一个大顶堆,只要保证放入堆中的是n-K+1个数字,当堆中数量过多时,弹出堆顶,最后返回堆顶的数字即可
class Solution {
public:
int findKth(vector<int> q, int n, int K) {
priority_queue<int,vector<int>,less<int>> p;
for(auto temp:q){
p.push(temp);
if(p.size()>n-K+1) p.pop();
}
return p.top();
}
};
平均时间复杂度:O(nlog(n))
空间复杂度:O(n)
桶排序相应定义如下
桶排序(Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后依次把各个桶中的记录列出来记得到有序序列。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,他不受到O(nlogn)下限的影响。
桶排序的思想近乎彻底的分治思想。
桶排序假设待排序的一组数均匀独立的分布在一个范围中,并将这一范围划分成几个子范围(桶)。
然后基于某种映射函数f ,将待排序列的关键字 k 映射到第i个桶中 (即桶数组B 的下标i) ,那么该关键字k 就作为 B[i]中的元素 (每个桶B[i]都是一组大小为N/M 的序列 )。
接着将各个桶中的数据有序的合并起来 : 对每个桶B[i] 中的所有元素进行比较排序 (可以使用快排)。然后依次枚举输出 B[0]….B[M] 中的全部内容即是一个有序序列。
补充: 映射函数一般是 f = array[i] / k; k^2 = n; n是所有元素个数
为了使桶排序更加高效,我们可以做到这两点:
在额外空间充足的情况下,尽量增大桶的数量 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
本篇更侧重代码解决,具体的动图演示和实现可以看这篇博客
桶排序
以上就是笔者关于TopK问题的解决