第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];
}
};