You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
上面我们实现了快速排序来取第 k 个最大值,其实没必要那么麻烦,我们仅仅需要在每执行一次快排的时候,比较基准值位置是否在 n-k 位置上,如果小于 n-k ,则第 k 个最大值在基准值的右边,我们只需递归快排基准值右边的子序列即可;如果大于 n-k ,则第 k 个最大值在基准值的做边,我们只需递归快排基准值左边的子序列即可;如果等于 n-k ,则第 k 个最大值就是基准值
代码实现:
letfindKthLargest=function(nums,k){returnquickSelect(nums,nums.length-k)};letquickSelect=(arr,k)=>{returnquick(arr,0,arr.length-1,k)}letquick=(arr,left,right,k)=>{letindexif(left<right){// 划分数组index=partition(arr,left,right)// Top kif(k===index){returnarr[index]}elseif(k<index){// Top k 在左边returnquick(arr,left,index-1,k)}else{// Top k 在右边returnquick(arr,index+1,right,k)}}returnarr[left]}letpartition=(arr,left,right)=>{// 取中间项为基准vardatum=arr[Math.floor(Math.random()*(right-left+1))+left],i=left,j=right// 开始调整while(i<j){// 左指针右移while(arr[i]<datum){i++}// 右指针左移while(arr[j]>datum){j--}// 交换if(i<j)swap(arr,i,j)// 当数组中存在重复数据时,即都为datum,但位置不同// 继续递增i,防止死循环if(arr[i]===arr[j]&&i!==j){i++}}returni}// 交换letswap=(arr,i,j)=>{lettemp=arr[i]arr[i]=arr[j]arr[j]=temp}
Activity
sisterAn commentedon Jun 7, 2020
目前已经刷了两道Topk问题,不过于三种方案:
k个k个最大元素小顶堆,取堆顶那么除了这两种方案还有没有其它的方式可解决本题喃?其实还有两种:
接下来一一解答😊
解法一:数组排序,取第 k 个数
最简单
代码实现:
复杂度分析:
解法二:构造前
k个最大元素小顶堆,取堆顶我们也可以通过构造一个前
k个最大元素小顶堆来解决,小顶堆上的任意节点值都必须小于等于其左右子节点值,即堆顶是最小值。所以我们可以从数组中取出
k个元素构造一个小顶堆,然后将其余元素与小顶堆对比,如果大于堆顶则替换堆顶,然后堆化,所有元素遍历完成后,堆中的堆顶即为第k个最大值具体步骤如下:
k个数(0到k-1位),构造一个小顶堆k位开始遍历数组,每一个数据都和小顶堆的堆顶元素进行比较,如果小于堆顶元素,则不做任何处理,继续遍历下一元素;如果大于堆顶元素,则将这个元素替换掉堆顶元素,然后再堆化成一个小顶堆。代码实现:
复杂度分析:
更多堆内容可查看 前端进阶算法9:看完这篇,再也不怕堆排序、Top K、中位数问题面试了
解法三:快速选择(quickselect)算法
无论是排序算法还是构造堆求解 Top k问题,我们都经过的一定量的不必要操作:
k的堆(大顶堆,小顶堆),时间复杂度也为O(nlogk)快速选择(quickselect)算法与快排思路上相似,我们先看看快排是如何实现的?
快排
快排使用了分治策略的思想,所谓分治,顾名思义,就是分而治之,将一个复杂的问题,分成两个或多个相似的子问题,在把子问题分成更小的子问题,直到更小的子问题可以简单求解,求解子问题,则原问题的解则为子问题解的合并。
快排的过程简单的说只有三步:
partition)具体按以下步骤实现:
注意这里的基准该如何选择喃?最简单的一种做法是每次都是选择最左边的元素作为基准,但这对几乎已经有序的序列来说,并不是最好的选择,它将会导致算法的最坏表现。还有一种做法,就是选择中间的数或通过
Math.random()来随机选取一个数作为基准,下面的代码实现就是以随机数作为基准。代码实现
快排是从小到大排序,所以第
k个最大值在n-k位置上复杂度分析
2n)2n)快速选择(quickselect)算法
上面我们实现了快速排序来取第 k 个最大值,其实没必要那么麻烦,我们仅仅需要在每执行一次快排的时候,比较基准值位置是否在
n-k位置上,如果小于n-k,则第 k 个最大值在基准值的右边,我们只需递归快排基准值右边的子序列即可;如果大于n-k,则第 k 个最大值在基准值的做边,我们只需递归快排基准值左边的子序列即可;如果等于n-k,则第 k 个最大值就是基准值代码实现:
复杂度分析:
时间复杂度:平均时间复杂度O(n),最坏情况时间复杂度为O(n2)
空间复杂度:O(1)
解法四:中位数的中位数(BFPRT)算法
又称为中位数的中位数算法,它的最坏时间复杂度为 O(n) ,它是由Blum、Floyd、Pratt、Rivest、Tarjan提出。该算法的思想是修改快速选择算法的主元选取方法,提高算法在最坏情况下的时间复杂度。
在BFPTR算法中,仅仅是改变了快速选择(quickselect)算法中
Partion中的基准值的选取,在快速选择(quickselect)算法中,我们可以选择第一个元素或者最后一个元素作为基准元,优化的可以选择随机一个元素作为基准元,而在 BFPTR 算法中,每次选择五分中位数的中位数作为基准元(也称为主元pivot),这样做的目的就是使得划分比较合理,从而避免了最坏情况的发生。BFPRT 算法步骤如下:
n/5个组,每组 5 个元素,若有剩余,舍去n/5个组中的每一组使用插入排序找到它们各自的中位数代码实现:
复杂度分析:
为什么是5?
在BFPRT算法中,为什么是选5个作为分组?
首先,偶数排除,因为对于奇数来说,中位数更容易计算。
如果选用3,有
,其操作元素个数还是
n。如果选取7,9或者更大,在插入排序时耗时增加,常数
c会很大,有些得不偿失。总结
所以,这里我们总结一下,求topk问题其实并不难,主要有以下几个思路:
leetcode
7777sea commentedon Jun 9, 2020
面向api开发👿
HPYAEyes commentedon Jun 9, 2020
将数组前k项堆化,构造出小顶堆,堆顶元素与剩余数组中每个元素比较,大于堆顶元素则替换,然后堆化,这样可以保证堆中元素全部大于数组中的元素,则堆顶元素为第k个最大元素
时间复杂度:O(nlogk)
空间复杂度:O(k)
cutie6 commentedon Dec 10, 2020
这里的时间空间复杂度是 js 原生 sort 方法的复杂度么?
TonyZhang1993 commentedon Jan 15, 2024
是的,详见#59 (comment)