题目
在未排序的数组中找到第 k 个最大的元素。请注意,它是数组有序排列后的第 k 个最大元素,而不是第 k 个不同元素。
例如,
给出 [3,2,1,5,6,4] 和 k = 2,返回 5。
注意事项:
你可以假设 k 总是有效的,1 ≤ k ≤ 数组的长度。
方法一
使用STL库函数 nth_element(),通过调用nth_element(start, start+n, end) 方法可以使第n大元素处于第n位置(从0开始,其位置是下标为 n的元素),并且比这个元素小的元素都排在这个元素之前,比这个元素大的元素都排在这个元素之后,但不能保证他们是有序的
代码:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
nth_element(nums.begin(),nums.end()-k,nums.end());
return *(nums.end()-k);
}
};
方法二
直接排序,然后取出第K大即可
代码:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
sort(nums.begin(),nums.end());
return nums[nums.size()-k];
}
};
方法三
利用multiset本身有序,维持multiset大小K,则multiset首元素即为所求.
注意: 由于数组本身存在重复元素,所以这里要用multiset
代码:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
multiset<int> s;
for(auto &m:nums){
s.insert(m);
if(s.size()>k) s.erase(s.begin());
}
return *s.begin();
}
};
方法四
借鉴快排原理,每次选择一个值,让其左边都比他小,右边都比他大,如果刚好是第n-k的位置,即为所求。如果小于n-k,则在他右边继续查找,否则在左边继续查找。
代码如下:
递归写法
class Solution {
public:
int getRes(vector<int> &nums,int k,int left, int right){
int tmp=nums[left],n=nums.size(),tleft=left,tright=right;
while(left<right){
while(right>left&&nums[right]>tmp) right--;
if(right>left){
nums[left++]=nums[right];
}
while(left<right&&nums[left]<=tmp) left++;
if(left<right) nums[right--]=nums[left];
}
nums[left]=tmp;
if(left==n-k) return tmp;
else if(left<n-k) return getRes(nums,k,left+1,tright);
else return getRes(nums,k,tleft,left-1);
}
int findKthLargest(vector<int>& nums, int k) {
int n=nums.size();
return getRes(nums,k,0,n-1);
}
};
非递归写法
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int n=nums.size(),tleft=0,tright=n-1;
while(tleft<tright){
int left=tleft,right=tright;
int tmp=nums[left];
while(left<right){
while(right>left&&nums[right]>tmp) right--;
if(right>left){
nums[left++]=nums[right];
}
while(left<right&&nums[left]<=tmp) left++;
if(left<right) nums[right--]=nums[left];
}
nums[left]=tmp;
if(left==n-k) return tmp;
else if(left<n-k){
tleft=left+1;
tright=tright;
}
else{
tleft=tleft;
tright=left-1;
}
}
return nums[tleft];
}
};
方法五
三段式快排,处理相等的情况,计算排序
class Solution {
public:
int parition(vector<int> &nums, int left, int right, int k) {
int pivot = nums[left];
int c_left = left, cl = left, c_right = right;
while(left <= right) {
if(nums[left] < pivot) swap(nums[left], nums[right--]);
else if(nums[left] > pivot) swap(nums[cl++], nums[left++]);
else left++;
}
if(cl + 1 == k || (cl + 1 < k && left-1 > k)) return pivot;
if(cl + 1 < k) return parition(nums, cl + 1, c_right, k);
else return parition(nums, c_left, cl - 1, k);
}
int findKthLargest(vector<int>& nums, int k) {
return parition(nums, 0, nums.size() - 1, k);
}
};
方法六
如果要求原始数组不能修改,同时要求只能使用常数空间复杂度,那么上述方法就全都不能用了。考虑用二进制解法。比如对于int型正整数,4字节,即32位,如果第i位 置1,保持前i-1位不变,那么根据前i位可以将数组分成两部分,即可以起到二分的作用。具体举例如下,数组为[2,6,4,1,3,5],然后找第2大的数,即k=2。int型对应32位bit标号为31,30,29...3,2,1,0:
| 十进制 | 二进制 |
|---|---|
2 | 10 |
6 | 110 |
4 | 100 |
1 | 1 |
3 | 11 |
5 | 101 |
1、bit位第2位为1,可以将数组分为[2,1,3]和[6,4,5]两部分。由于需要找第2大,所以在数组[6,4,5]中继续查找。
2、继续考察bit位第1位为1,则数组[6,4,5]可以分为[4,5]和[6]两部分,此时较大的数组为[6]只有一个元素,所以在较小的数组[4,5]中继续找第1大的数(这里要注意一下,由于[6]只有一个,所以k=k-1=1)
3、继续考察第0位为1,则数组[4,5]可以分为[4]和[5]两组,较大数组[5]只有1个,而此时k=1,故可以直接取出5即为所求。
但整个过程中,由于只能使用常数空间,所以无法保存数组,故可以考虑用一个变量last来判断当前元素是否需要考虑。last用于保存已遍历高位取值,若元素高位与last一致,则当前元素是需要考虑的,否则当前元素不用考虑。用一个变量mask做与运算取出元素已遍历高位值。以上述示例具体演示如下:
1、由于第31至3位均为0,故起始last=0,mask=1111 1111 1111 1111 1111 1111 1111 1(2)。针对元素m,则cur=mask<<3,然后cur&m即为m对应已考虑高位,如果((last<<3)^(cur&m))==0,则表示当前元素高位部分符合要求,需要考虑,否则不考虑。
此时数组[2,6,4,1,3,5]均符合要求,需要考虑。执行上述过程之后,分为两个组[2,1,3]和[6,4,5],此时由于较大数组个数为3大于k,所以较小数组抛弃,last=1,mask=1111 1111 1111 1111 1111 1111 1111 11(2)
2、继续考察bit位第1位,遍历数组中元素,针对每个元素计算cur=mask<<2,计算cur&m取出高位部分,然后计算((last<<2)^(cur&m))==0则当前元素考虑。对于元素1,2,3计算后不等于0,故均为抛弃元素,而4,5,6计算均为0,故需考虑第1位,根据其是否为1分为两组[4,5]和[6]。由于较大数组[6]只有一个元素,小于k,抛弃,考虑在较小数组[4,5]中继续查找。此时k=k-1=1,last=10(2),mask=1111 1111 1111 1111 1111 1111 1111 111(2)
3、继续考察第0位,遍历数组中元素,针对每个元素计算cur=mask<<1,计算cur&m取出高位部分,然后计算((last<<1)^(cur&m))==0则当前元素考虑。对于元素1,2,3,6计算后不等于0,故均为抛弃元素,而4,5计算均为0,需要考虑第0位,根据第0为是否为1,分为[4]和[5]两部分,此时较大数组只有一个元素,k=1,则5即为所求。
注意:
判断是否相同时用异或运算
数组中可能存在负数,负数二进制用补码表示,最高位为1,所以可以先判断负数和正数的个数,确定在哪个区间查找。如果是在负数部分查找,那么last起始值为1,否则last起始值为0。
代码如下:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int left=0,right=0,last=0,cur=0,mask=1; //数组划分两部分,left保存较小部分元素个数,right保存较大部分元素个数
int i,j;
//处理负数,负数最高位为1,正数为0,用以确定last起始值。
for(auto &m:nums){
if(((1<<31)&m)!=0) left+=1;
else right+=1;
}
if(right<k){
last=1;
k=k-right;
}
for(i=31;i>0;i--){
cur=mask<<i;
left=0,right=0;
for(j=0;j<nums.size();j++){
if(((last<<i)^(cur&nums[j]))==0){ //用来判断当前元素是否需要考虑
if(((1<<(i-1))&nums[j])>0) right+=1;
else left+=1;
}
}
//跳出循环条件,如果k=1,且较大部分刚好只有一个元素,即为所求,或者较大部分无元素,而较小部分只有一个元素,即为所求
if(right==1&&k==1){
last=last<<i;
last=last|(1<<(i-1));
cur=cur|(1<<(i-1));
break;
}
else if(k==1&&left==1&&right==0){
last=last<<i;
cur=cur|(1<<(i-1));
break;
}
last=last<<1;
if(right<k) k=k-right; //由于考虑第K大,所有较大部分抛弃,K中需要减去对应的个数
else last=last|1;
mask=(mask<<1)|1;
}
if(i==0) return last; //如果32位全部考虑,那么此时last的值即为所求
for(auto &m:nums){
if((last^(cur&m))==0) return m;
}
return last>>1;
}
};
方法六(常数空间)
用二分查找的方法来解决这道题
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int left=nums[0],right=nums[0];
for(auto it=nums.begin();it!=nums.end();it++){
left=min(left,*it);
right=max(right,*it);
}
int mid=(left+right)/2,count=0,flag=0,last=-1;
while(count!=k){
count=0,flag=0;
mid=(left+right)/2;
for(int i=0;i<nums.size();i++){
if(mid==nums[i]) flag=1;
if(nums[i]>=mid) count+=1;
}
if(count>k) left=mid+1;
else if(count<k) right=mid-1;
if(last==count) break;
else last=count;
}
if(flag==1&&count==k) return mid;
else if(count>k&&left==right-1) return right;
else{
int res=right;
for(int i=0;i<nums.size();i++){
if(nums[i]>mid){
res=min(res,nums[i]);
}
}
return res;
}
}
};

291





