【LeetCode】215. Kth Largest Element in an Array

0、算法设计

考虑这样一种分治算法。对于任意给定的数v,假设S中的数被分成三组:比v小的数、与v相等的数(可能会有多个)以及比v大的数。分别记这三组数为SL、SV和SR。例如,如果集合S如下所示:
S : 2 ∣ 36 ∣ 5 ∣ 21 ∣ 8 ∣ 13 ∣ 11 ∣ 20 ∣ 5 ∣ 4 ∣ 1 S:2|36|5|21|8|13|11|20|5|4|1 S2365218131120541
针对v=5,S被分为三组,分别为
S L : 2 ∣ 4 ∣ 1 S_L: 2|4|1 SL:241
S V : 5 ∣ 5 S_V: 5|5 SV:55
S R : 36 ∣ 21 ∣ 8 ∣ 13 ∣ 11 ∣ 20 S_R: 36|21|8|13|11|20 SR:36218131120
搜索范围立即缩小,转而在S的这三个子集中的某一个继续进行。如果我们想要寻找集合S的第8小元素,我们知道它一定是SR的第3小元素,因为|SL|+|Sv|=5,即 s e l e c t i o n ( S , 8 ) = s e l e c t i o n ( S R , 3 ) selection(S,8)=selection(S_R,3) selection(S,8)=selection(SR,3)
更一般的,通过比较k与这些子集所含元素数之间的大小关系,我们可以很快确定所要寻找的元素在哪个子集中:
s e l e c t i o n ( S , k ) = { s e l e c t i o n ( S L , k ) k ≤ ∣ S L ∣ v ∣ S L ∣ &lt; k ≤ ∣ S L ∣ + ∣ S V ∣ s e l e c t i o n ( S L , k − ∣ S L ∣ − ∣ S V ∣ ) k &gt; ∣ S L ∣ + ∣ S V ∣ selection(S,k)=\left\{ \begin{array}{} selection(S_L,k)&amp; &amp; {k \leq |S_L|}\\ v &amp; &amp; {|S_L| &lt; k \leq |S_L|+|S_V|}\\ selection(S_L,k-|S_L|-|S_V|) &amp; &amp; { k &gt; |S_L|+|S_V|}\\ \end{array} \right. selection(S,k)=selection(SL,k)vselection(SL,kSLSV)kSLSL<kSL+SVk>SL+SV

1、算法的效率

如果每次选的v恰好是中项,则它的运行时间将满足
T ( n ) = T ( n / 2 ) + O ( n ) T(n)=T(n/2)+O(n) T(n)=T(n/2)+O(n)
即O(n)

但我们不可能每次选中的v正好是中项,我们可以采取一个简单的策略:从S中随机选取v

很显然,算法的运行时间依赖于我们随机选取的v。完全有可能出现这样的情况:由于持续的背运,我们挑选出来的v总是数组中最大的元素(或是最小元素)。这样的话,每次我们只能将待搜索的数组大小缩减1。对应的,在之前的例子里,我们可能首先选出的v=36,然后选出v=21,以此类推。这种最差情况将使得我们的选择问题算法必须要执行
n + ( n − 1 ) + ( n − 2 ) + . . . + n 2 = O ( n 2 ) n+(n-1)+(n-2)+...+\frac n2 = O(n^2) n+(n1)+(n2)+...+2n=O(n2)
次操作(当寻找中项时)。但是,这种情况出现的概率还是很低的。同样很少出现的还有最佳情形,即每次随机选取的v刚好能将数组一分为二,从而使算法的运行时间达到O(n)。在O(n)和O(n2)的范围内,算法的平均运行时间是多少呢?幸运的是,它与最佳情形下的运行时间很接近。

我们规定,当v落在它所在数组中的四分之一位置和四分之三位置之间,这样的v是好的。则一个随机选中的v较好的概率是50%,则在平均两次划分操作之后,待搜索数组的大小将最多缩减至原始大小的四分之三。

基于这样的引理:在掷硬币游戏之中,在得到一次正面之前,平均需要掷一个匀质硬币2次。

则我们可以得到算法的期望时间:
T ( n ) ≤ T ( 3 n 4 ) + O ( n ) T(n)\leq T( \frac {3n}4) + O(n) T(n)T(43n)+O(n)
由该递推式可得T(n)=O(n)
即算法的运行时间为O(n)

2、代码实现

int findKthLargest(vector<int>& nums, int k) {
	srand (time(NULL));
    vector<int> left, right;
    int leftCount = 0, middleCount = 0, rightCount = 0;
    int numSelected = nums[(rand() % nums.size())];
    for(auto num : nums) {
    	if(num < numSelected) {
    		left.push_back(num);
    		leftCount++;
    	} else if(num == numSelected) {
    		middleCount++;
    	} else {
    		right.push_back(num);
    		rightCount++;
    	}
    }
    if(k <= rightCount) {
    	return findKthLargest(right, k);
    } else if(k > rightCount && k <= rightCount + middleCount) {
    	return numSelected;
    } else {
    	return findKthLargest(left, k - rightCount - middleCount);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值