TopK问题三种方法总结

TopK问题三种方法总结

文章目录

  • TopK问题三种方法总结
    • what's topK?
      • 解题步骤
      • 具体实现
      • summary
    • quickSelect
      • 解题步骤
      • 具体实现
      • summary
    • 二分法
      • 解题步骤
      • 具体实现
      • summary
      • leetcode-719-找出第 k 小的距离对
      • leetcode-378-有序矩阵中第K小的元素

references:

Top K问题的两种解决思路

TOP-K问题的几种解法

Top-K问题的几种求解方法

快速选择排序 Quick select 解决Top K 问题

what’s topK?

topK问题是实际应用中涉及面较广的一个抽象问题,譬如:从20亿个数字的文本中,找出最大的前100个。

具体可以参考我的另一篇文章:数据结构javascript描述中对于heap的总结。

解题步骤

  • 首先把数组中的前k个值用来建一个小根堆(不是大根堆)。
  • 之后的其他数拿到之后进行判断,如果遇到比小顶堆的堆顶的值大的,将堆顶元素删除,将该元素放入堆中
  • 最终的堆会是数组中最大的K个数组成的结构,小根堆的顶部又是结构中的最小数,因此把堆顶的值弹出即可得到Top-K。

具体实现

// 注意如果求的不是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];
};

summary

  • 时间复杂度: O(NlogK)其中N为数组全部长度,K即为要求的K(因为堆中元素的数目永远是K

quickSelect

解题步骤

  • swap函数交换元素位置
  • partition 按照快排的分割思想,pivot左边是比pivot小的所有数,返回pivot所在位置
  • 核查pivot-left+1和k大小比较
    • 如果大于k那么topK就在pivot左侧的这些数里面
    • 如果等于k那么topK就是pivot所在位置的值
    • 如果小于k那么寻找pivot右侧的topk-(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);
};


summary

quickSelect方法 与Quick sort不同的是,Quick select只考虑所寻找的目标所在的那一部分子数组,而非像Quick sort一样分别再对两边进行分 割。正是因为如此,Quick select将平均时间复杂度从O(nlogn)降到了O(n),但与此同时,QuickSelect与QuickSort一样,是一个不稳定的算法;pivot选取直接影响了算法的好坏,worst case下的时间复杂度达到了O(n^2)

  • 时间复杂度最大为:O(n^2)
  • 时间复杂度最小为:O(n)
  • 空间复杂度:O(1)

二分法

关于二分查找法的详解可以参考我的另一篇文章:详解二分查找法

解题步骤

  • 找到最大值max和最小值min
  • 计算mid值开始循环查找
    • 注意我们的max可以等于min,因为max min均为数组中的值
    • 如果比mid大的数目大于等于k,那么mid过小,min变成mid+1
      • 为什么要把等于k的情况加入呢,因为等于k的情况下,选取的mid是小于等于目标值的
    • 如果比mid大的数目小于k,那么mid过大,max变成mid-1
  • 返回max值即为我们想要的

具体实现

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;
};

summary

  • 时间复杂度:O(nlog(max-min)) (不确定)
  • 空间复杂度:O(1)

leetcode-719-找出第 k 小的距离对

首先这个题就是典型的topk问题,但是用构建最大堆的方式并不能通过,因此这边可以考虑用二分法的方式解决,既然采用二分法套用上面模板即可,但是难点在于如何遍历所有找出来小于mid的所有距离对的个数呢?(遍历所有显然不现实)

此处查找count数目用到了双指针:

  • 维护一个right 递增
  • 查找一个最小的left满足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;
};

leetcode-378-有序矩阵中第K小的元素

解题思路可以参考我的题解二分法解决or最小堆解决(此处不赘述)

你可能感兴趣的:(Algorithm)