LeetCode-Kth Largest Element in an Array
来源:互联网 发布:淘宝seo常见的问题 编辑:程序博客网 时间:2024/06/01 09:08
Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.
For example,
Given [3,2,1,5,6,4]
and k = 2, return 5.
Note:
You may assume k is always valid, 1 ≤ k ≤ array's length.
题目的意思很明确,也很简单,就是要找出第k大的数。并且特意标注了,是在有序列中的第k大的元素,不是第k大的不重复的元素。
既然是找出第k大的元素,那么对这个数组进行排序,按照降序的话,返回a[k-1]就好了。这么做的时间复杂度是O(n*log(n)),其中n是数组的长度。虽然也是复杂度很好的一个算法了,但是还是有浪费的,我们只需要第k大的元素即可,不需要保证其他元素的有序性。那么,接下来就对排序算法进行一些改动,令其更加的适合这个题目,只需要找出第k大的元素就可以结束了。
首先来看第一种方法,对快排进行改动,在找到第k大的元素时就停止。
先来回顾下快排的思想:选取一个数,根据这个数,把数组分成三部分,第一部分是大于这个书的,第二部分是等于这个数的,第三部分是小于这个数的。然后继续把每个部分都进行分解直到每个部分的大小为一。
那么,如何将这种思想运用到这个题目上呢?依旧是划分,分成两部分,根据这两部分的大小来决定,第k大的元素是处在哪一部分。也就是选取一个数key,根据key把数组分成两部分S1 和S2,如果|S1| < k,那么第k大的元素必定在S2中,并且是S2中第(k-|S1|)大的元素;否则,第k大的元素在S1中,并且也是S1中第k大的元素。那么,在什么时候停止呢?那就是当|S1|+1 == k时,也就是我们选取的key刚好是第k大的元素时。这样思路就清晰了,这部分的代码如下:
int findKthLargest(vector<int>& nums, int s, int e, int k) { int key = nums[s+k-1]; int p = divid(nums, s, e, k); if (p+1 == k) return key; else if (p >= k) { return findKthLargest(nums, s, s+p, k); } else { return findKthLargest(nums, s+p, e, k-p); } }那么剩下的问题就是如何划分了。我们选定一个元素key,然后在有效范围内遍历整个数组,如果a[i] > key,那么就把a[i]放到数组的未排序部分的头部,这样,当遍历结束后,就找出了划分的边界。具体代码如下:
int divid(vector<int>& nums, int s, int e, int k) { int key = nums[s+k-1]; int p = 0; swap(nums, s+k-1, e-1); for (int i = s; i < e-1; ++i) { if (nums[i] >= key) { swap(nums, i, s+p); ++p; } } swap(nums, e-1, s+p); return p; }需要注意的是对等于key值的处理,我这里是把除key以外的所有等于key的元素都放在第1部分,而把key放在了第2部分。这样做的理由如下:当数组的元素全都相同时,可以避免把全部的元素都放在一个部分而导致另一个部分为空,从而进一步避免了无限的函数调用。
这样下来,整个算法就完成了。和快排一样,这样算法也不是一个稳定的算法,算法的时间复杂度在很大程度上取决于key值的选择。如果按照平均效率来计算,即每次划分都能把数组的搜索范围减半,那么有递归方程:T(n) = T(n/2) + O(n)。 这样有T(n) = O(n)。当然这只是平均效率,最坏效率依然会有T(n) = T(n-1) + O(n) = O(n^2)。
class Solution {public: int findKthLargest(vector<int>& nums, int k) { return findKthLargest(nums, 0, nums.size(), k); } int findKthLargest(vector<int>& nums, int s, int e, int k) { int key = nums[s+k-1]; int p = divid(nums, s, e, k); if (p+1 == k) return key; else if (p >= k) { return findKthLargest(nums, s, s+p, k); } else { return findKthLargest(nums, s+p, e, k-p); } } int divid(vector<int>& nums, int s, int e, int k) { int key = nums[s+k-1]; int p = 0; swap(nums, s+k-1, e-1); for (int i = s; i < e-1; ++i) { if (nums[i] >= key) { swap(nums, i, s+p); ++p; } } swap(nums, e-1, s+p); return p; } void swap(vector<int>& nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }};好了,基于快排的方法就到这里了。接下来,介绍基于堆排的算法。
堆排的重点在于建堆和维护堆。建堆是把整个数组的次序进行重整使其满足最大堆或者最小堆。而堆的维护是指,当把堆顶部的元素取走时,对剩下的元素进行调整,使其依然满足最大堆或者最小堆。
对于此题,我们需要建立并维护一个最大堆,即每个元素都比其子元素要大,然后通过k-1次的取出堆顶元素并进行堆的维护,那么在第k次,堆顶的元素,就是第k大的元素。
首先是对堆的重建的代码,因为每次最多有一半的元素需要调整,所以重建的时间复杂度是O(log(n)):
void rebulidHeap(vector<int>& nums, int s, int e) { int left = 2*s+1; int right = 2*s+2; if (left >= e) return ; int big = left; if (right < e && nums[right] > nums[big]) big = right; if (nums[s] < nums[big]) { swap(nums, s, big); rebulidHeap(nums, big, e); } }然后是建堆,时间复杂度是O(n):
void bulidHeap(vector<int>& nums) { for (int i = nums.size()-1; i > 0; --i) { int parent = (i-1)/2; if (nums[i] > nums[parent]) { swap(nums, i, parent); rebulidHeap(nums, i, nums.size()); } } }最后是获取第k大的元素,时间复杂度是O(k*log(n)):
for (int i = 0; i < k-1; ++i) { swap(nums, 0, nums.size()-i-1); rebulidHeap(nums, 0, nums.size()-i-1); }这样,总体的代码是,那么时间复杂度为建堆的O(n)加上获取目标的O(k*log(n)),所以总体的时间复杂度为O(n+k*log(k))。虽然在平均时间复杂度上比快排要高,但是它是一个稳定的算法,不会出现快排的那种复杂度上升到O(n^2)的情况:
class Solution {public: int findKthLargest(vector<int>& nums, int k) { bulidHeap(nums); for (int i = 0; i < k-1; ++i) { swap(nums, 0, nums.size()-i-1); rebulidHeap(nums, 0, nums.size()-i-1); } return nums[0]; } void rebulidHeap(vector<int>& nums, int s, int e) { int left = 2*s+1; int right = 2*s+2; if (left >= e) return ; int big = left; if (right < e && nums[right] > nums[big]) big = right; if (nums[s] < nums[big]) { swap(nums, s, big); rebulidHeap(nums, big, e); } } void bulidHeap(vector<int>& nums) { for (int i = nums.size()-1; i > 0; --i) { int parent = (i-1)/2; if (nums[i] > nums[parent]) { swap(nums, i, parent); rebulidHeap(nums, i, nums.size()); } } } void swap(vector<int>& nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }};
- Leetcode Kth Largest Element in an Array
- Leetcode: Kth Largest Element in an Array
- LeetCode Kth Largest Element in an Array
- [LeetCode] Kth Largest Element in an Array
- [leetcode] Kth Largest Element in an Array
- leetcode--Kth Largest Element in an Array
- #leetcode#Kth Largest Element in an Array
- LeetCode Kth Largest Element in an Array
- 【Leetcode】Kth Largest Element in an Array
- Leetcode: Kth Largest Element in an Array
- Kth Largest Element in an Array -- leetcode
- [Leetcode]Kth Largest Element in an Array
- [LeetCode]Kth Largest Element in an Array
- *LeetCode-Kth Largest Element in an Array
- LeetCode----Kth Largest Element in an Array
- LeetCode Kth Largest Element in an Array
- leetcode-Kth Largest Element in an Array
- LeetCode -- Kth Largest Element in an Array
- Java 学习感受
- opencv(c++)基本绘图
- 11.3
- Linux文件打包与压缩
- three.js 02-04 之网格对象函数及属性
- LeetCode-Kth Largest Element in an Array
- python的读取纯文本文件的几种模式
- 算法---最小公倍数和最大公约数
- html表格基础及案例示图代码。
- 数据结构-Java实现【整理】
- Material Design学习之悬浮按钮——FloatingActionBar(3)
- Codeforces Round #444 (Div. 2) A. Div. 64
- css的主要学习内容总结
- 第九周项目三 利用二叉树遍历思想解决问题(4)