在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
分析:或许很多人面试的时候被问到过10万个数里面取第k大的数算法怎么设计,这种属于海量数据问题,思路又要变化下,你回答的时候肯定不可能直接给出一个算法就结束了,主要是如何针对不同场景和限制如何给出最优解,这就很考验基本功了。这里面最核心的就是考虑时间和内存的限制了。
先不管海量数据的情景,回到这题,我们一般考虑哪些思路。
1 最直接的想法,先对整个数组进行排序,然后取第k大的数。
至于如何排序,那内容就多了。比如快排,堆排,偷懒的人可能直接调用STL里的sort函数就过了,比如可以选择快排,比竟快排平均花费时间最少。
时间复杂度O(nlogn)
2 利用快速选择的想法。
也就是快排的分治思想。取一个指针pivot
- 如果pivot == k,则正好找到了第k小的元素
- 如果pivot > k,则第k小的元素存在于pivot左边
- 如果pivot < k,则第k小的元素存在于pivot右边
3 利用最小堆
维护一个容量为k的最小堆,在建完堆后将取堆内第k个数即可。
关于快速选择的算法可以参考下算法导论。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
sort(nums.rbegin(), nums.rend());
return nums[k - 1];
}
};
class Solution {
public int findKthLargest(int[] nums, int k) {
shuffle(nums);
k = nums.length - k;
int low = 0;
int high = nums.length - 1;
while(low < high) {
int pivot = partition(nums, low, high);
if(pivot < k) {
low = pivot + 1;
} else if(pivot > k) {
high = pivot - 1;
} else {
break;
}
}
return nums[k];
}
//参考算法第四版
int partition(int[] a, int low, int high) {
int i = low;
int j = high + 1;
while(true) {
while(j < high && less(a[++i], a[low]));
while(j > low && less(a[low], a[--j]));
if(i >= j) {
break;
}
exch(a, i, j);
}
exch(a, low, j);
return j;
}
void exch(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
boolean less(int v, int w) {
return v < w;
}
void shuffle(int a[]) {
//在partition前将数组顺序打乱,保证不出现最坏情况。
Random random = new Random();
for(int i = 1; i < a.length; i++) {
int r = random.nextInt(i + 1);
exch(a, i, r);
}
}
}
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
for(int val : nums) {
pq.offer(val);
if(pq.size() > k) {
pq.poll();
}
}
return pq.peek();
}
}
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
// 使用递减greater<int>函数对象排序
priority_queue<int,vector<int>,greater<int>> pq;
for(int i = 0; i < nums.size(); i++) {
pq.push(nums[i]);
if(pq.size() > k) {
pq.pop();
}
}
//返回队顶元素
return pq.top();
}
};
如果情境改为10亿个数的topK问题,怎么想。
最好想的,还是先全部排序,然后查找。
最快的排序算法的时间复杂度一般为O(nlogn),如快速排序。
但是如果计算机内存不够大,这种方法很容易受内存限制。
主要解决方案:
-
分治+Trie树
-
hash+小顶堆
局部淘汰法
用一个容器保存前1万个树,然后将剩余的所有数字一一和容器内最小的数字比较,如果后续元素比容器内的最小元素大就删掉容器内的最小元素,并将该元素插入容器,最后遍历玩这1亿个数,得到最终结果。
分治
将1亿个数据分成100份,每份100万个数据,找出每份数据中最大的1万个,最后在剩下的100 X 10000个数据。也就是100万个数据里面找前1万
哈希
通过hash法,把这1亿个数字去重复,这样如果重复率很高的话,会减少很大的内存用量,从而缩小运算空间,
最小堆
先读入前10000个数来创建大小为10000的小顶堆,建堆的时间复杂度为O(mlogm),然后遍历后续数字,并与堆顶数字(最小)进行比较,如果比堆顶的数字大,替换堆顶元素调整堆为小顶堆。这个过程直到1亿个数全部遍历完。然后按照中序遍历的方式输出当前堆中的所有1万个数字。该算法的时间复杂度为O(nmlogm),空间复杂度是常数。