第215题 数组中的第K个最大元素(双解法—快速排序和堆排序)
问题分析
这里是该题的链接。题目的目的就是求一个无序数组排序后第k大的元素,其实可以这样理解:就是求排序后下标为n-k的数(n为数组的长度)。
我开始用的是快速排序的方法,但是后来提交后就很慢。后来看了别人的思路,就自己用堆排序重做了一遍,性能优化了很多。
下面就介绍一下这两种解法:
快速排序
关于快速排序的帖子网上很多,不清楚的人可以去搜一搜。
快速排序比较重要的一点就是每次用来pivot的数,在这一轮排序结束后落在的位置肯定是它在最终的排序好序列中的位置。
既然这样,我们就可以利用这个思想找到某个特定位置上在排序后的数。前面说过,题目可以转化为找下标为n-k的数,用快速排序方法可以一步一步的“逼近”这个位置。具体看下面的例子:
假设数组为[3, 2, 3, 1, 2, 4, 5, 5, 6],要求的是第4个最大元素,即下标为5的元素。那么第一轮排序用3做为pivot,最终3的位置为3,小于5,下一轮就排序下标为3后面的数组。同样取3作为pivot,求的下标为4,仍然小于5,继续上述操作。最后,pivot为4的时候,它的位置正好为5,所以最终返回的就是为4。
堆排序
堆排序的讲解网上也有很多,不清楚的可以去看看。堆分为大根堆(即所有父节点值都大于子节点,堆顶为整个数最大的数),小根堆(即所有父节点值都小于子节点,堆顶为整个数最小的数)。这里我们选用小顶堆。先用数组的前k个元素初始化一个小顶堆,此时堆顶就是前k个数中最小的数。从第k+1开始遍历,如果遍历的数小于等于堆顶,不做任何处理,如果大于,那么替换堆顶,并对堆进行调整,使得堆顶仍是整个树最小的节点。
具体的例子如下:
第一次取4个数组建一个堆,堆顶为2。然后从k+1个数开始遍历,第一次为1,小于堆顶不做处理。第二次为4,大于2,那么先替换堆顶,后进行调整。重复上述操作,一直到整个遍历结束。
两种方法的对比
快速排序的时间复杂度为O(nlogn),这种方法对于有些情况会比较慢,因为我选的是每次用第一个数作为pivot,假如恰好这个数都最小的(比如一个递增的数组),导致每次排序都会扫描数组一遍,最终的复杂度很可能到O(n2)。
堆排序时间复杂度为O(nlogk)~O(n),总的来说时间复杂度要小很多。
源码
最近在学习C++,所以两种方法都用来Python和C++来写的
快速排序
Python
class Solution:
def findKthLargest(self, nums, k):
def quickSort(l, r):
pivot = nums[l]
left = l + 1
right = r
while True:
while right >= left and nums[right] >= pivot:
right -= 1
while right >= left and nums[left] < pivot:
left += 1
if (left >= right):
break
nums[right], nums[left] = nums[left], nums[right]
nums[right], nums[l] = nums[l], nums[right]
return right
n = len(nums)
left = 0
right = n - 1
k = n - k
while True:
ret = quickSort(left, right)
if ret == k:
return nums[ret]
elif ret > k:
right = ret-1
else:
left = ret+1
C++
class Solution {
int quickSort(vector<int>& vec,int first, int r){
int pivot = vec[first];
int l = first + 1;
while (true){
while (r >= l && vec[r] >= pivot)
--r;
while (r > l && vec[l] < pivot)
++l;
if (l >= r)
break;
int temp = vec[r];
vec[r] = vec[l];
vec[l] = temp;
}
int temp = vec[first];
vec[first] = vec[r];
vec[r] = temp;
return r;
}
public:
int findKthLargest(vector<int>& nums, int k) {
int first = 0;
int n = nums.size();
int r = n - 1;
k = n - k;
cout << k << endl;
while (true){
int ret = quickSort(nums, first, r);
if (ret == k)
return nums[k];
else if (ret > k)
r = ret - 1;
else
first = ret + 1;
}
}
};
堆排序
Python
class Solution:
def findKthLargest(self, nums, k):
minHeap = nums[:k]
def adjustHeap(i):
child = 2 * i + 1
while child < k:
if child + 1 < k and minHeap[child] > minHeap[child+1]:
child +=1
if minHeap[child] >= minHeap[i]:
break
minHeap[i], minHeap[child] = minHeap[child], minHeap[i]
i = child
child = 2 * i + 1
for i in range((k-2) // 2, -1, -1):
adjustHeap(i)
for num in nums[k:]:
if num > minHeap[0]:
minHeap[0] = num
adjustHeap(0)
return minHeap[0]
C++
class Solution {
void adjustHeap(vector<int> &heap, int i, int& k){
int child = i * 2 + 1;
while (child < k){
if (child + 1 < k && heap[child] > heap[child + 1])
++child;
if (heap[child] >= heap[i])
break;
int temp = heap[i];
heap[i] = heap[child];
heap[child] = temp;
i = child;
child = 2 * i + 1;
}
}
public:
int findKthLargest(vector<int>& nums, int k) {
vector<int> minHeap;
minHeap.assign(nums.begin(), nums.begin()+k);
for (int i = (k - 2) / 2 ; i >=0; --i)
adjustHeap(minHeap, i, k);
for (auto iter = nums.begin()+k; iter != nums.end(); ++iter){
if (*iter > minHeap[0]){
minHeap[0] = *iter;
adjustHeap(minHeap, 0, k);
}
}
return minHeap[0];
}
};