Top K问题的两种解决思路
TOP-K问题的几种解法
Top-K问题的几种求解方法
快速选择排序 Quick select 解决Top K 问题
topK问题是实际应用中涉及面较广的一个抽象问题,譬如:从20亿个数字的文本中,找出最大的前100个。
具体可以参考我的另一篇文章:数据结构javascript描述中对于heap
的总结。
// 注意如果求的不是topK而是lowK则是用 大根堆
import Heap from './algorithm/Heap/MinHeap';
const topK=(arr,k)=>{
let h=new Heap();
for(let i=0;i<k;i++){
h.insert(arr[i]);
}
for(let i=k;i<arr.length;i++){
if (arr[i]>h.data[0]){
h.deleting();
h.insert(arr[i]);
}
}
return h.data[0];
};
O(NlogK)
其中N
为数组全部长度,K
即为要求的K
(因为堆中元素的数目永远是K
)k-(pivot-left+1)
即可const swap=(arr,a,b)=>{
let temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
};
const partition=(arr,k,left,right)=>{
let pivot=left,lessThan=left;
if(left===right) return left;
for(let i=left;i<=right;i++){
if(arr[i]<arr[pivot]){
lessThan++;
swap(arr,lessThan,i);
}
}
swap(arr,lessThan,pivot);
return lessThan;
};
const quickSelect=(arr,k,left,right)=>{
let idx=partition(arr,k,left,right);
// 一定要注意:idx已经被检查过所以不能再将其加入重新进行检查
if(right-idx+1>k){
return quickSelect(arr,k,idx+1,right);
}else if(right-idx+1===k){
return arr[idx];
}else{
return quickSelect(arr,k-(right-idx+1),left,idx-1);
}
};
const topK=(arr,k)=>{
return quickSelect(arr,k,0,arr.length-1);
};
quickSelect
方法 与Quick sort不同的是,Quick select只考虑所寻找的目标所在的那一部分子数组,而非像Quick sort一样分别再对两边进行分 割。正是因为如此,Quick select将平均时间复杂度从O(nlogn)
降到了O(n)
,但与此同时,QuickSelect与QuickSort一样,是一个不稳定的算法;pivot选取直接影响了算法的好坏,worst case下的时间复杂度达到了O(n^2)
关于二分查找法的详解可以参考我的另一篇文章:详解二分查找法
const topK2=(arr,k)=>{
let low=Number.MAX_SAFE_INTEGER,high=-Number.MAX_SAFE_INTEGER;
for(let i=0;i<arr.length;i++){
if(arr[i]<low){
low=arr[i];
}
if(arr[i]>high){
high=arr[i];
}
}
// count用来计算从大于mid小于high的数字的个数
let cn;
while(low<=high){
let mid=Math.floor((low+high)/2);
cn=count(arr,mid);
// console.info('===>',{low,high},cn,mid);
if(cn>=k){
// mid is too small
low=mid+1;
}else{
// mid is too big
high=mid-1;
}
}
return high;
};
// =========>扩展
/**
* 如何改造成求最小的第k个值呢
* @param arr
* @param k
* @returns {number}
*/
const topK3=(arr,k)=>{
const count1=(arr,target)=>{
let count=0;
for(let i=0;i<arr.length;i++){
if(arr[i]<=target){
count++;
}
}
return count;
};
let low=Number.MAX_SAFE_INTEGER,high=-Number.MAX_SAFE_INTEGER;
for(let i=0;i<arr.length;i++){
if(arr[i]<low){
low=arr[i];
}
if(arr[i]>high){
high=arr[i];
}
}
// count用来计算从大于mid小于high的数字的个数
let cn;
while(low<=high){
let mid=Math.floor((low+high)/2);
cn=count1(arr,mid);
// console.info('===>',{low,high},cn,mid);
if(cn>=k){
// mid is too big
high=mid-1;
}else{
// mid is too small
low=mid+1;
}
}
return low;
};
首先这个题就是典型的topk问题,但是用构建最大堆的方式并不能通过,因此这边可以考虑用二分法的方式解决,既然采用二分法套用上面模板即可,但是难点在于如何遍历所有找出来小于mid的所有距离对的个数呢?(遍历所有显然不现实)
此处查找count数目用到了双指针:
arr[right]-arr[left]<=mid
right-left
的值即为小于mid的所有距离对个数。(至于为何是right-left
可以通过自己举例验证比如1到4之间有多少距离对:3+2+1
)const count=(arr,target)=>{
let res=0,left=0;
for(let right=0;right<arr.length;right++){
while(arr[right]-arr[left]>target)left++;
res+=right-left;
}
return res;
};
const smallestDistancePair1=(arr,k)=>{
arr.sort((a,b)=>a-b);
let min=0,max=arr[arr.length-1]-arr[0];
while(min<=max){
let mid=Math.floor((min+max)/2);
let cn=count(arr,mid);
if(cn>=k){
// mid过大,===k的情况时有可能取的mid也是一个大于目标值的数
max=mid-1;
}else{
min=mid+1;
}
}
return min;
};
解题思路可以参考我的题解二分法解决or最小堆解决(此处不赘述)