1.題目
題目地址(215. 陣列中的第K個最大元素 - 力扣(LeetCode))
https://leetcode.cn/problems/kth-largest-element-in-an-array/
題目描述
給定整數陣列 nums
和整數 k
,請返回陣列中第 k
個最大的元素。
請注意,你需要找的是陣列排序後的第 k
個最大的元素,而不是第 k
個不同的元素。
你必須設計並實現時間複雜度為 O(n)
的演算法解決此問題。
示例 1:
輸入:[3,2,1,5,6,4],
k = 2 輸出: 5
示例 2:
輸入:[3,2,3,1,2,4,5,5,6],
k = 4 輸出: 4
提示:
1 <= k <= nums.length <= 105
-104 <= nums[i] <= 104
2.題解
可以參考P1923 【深基9.例4】求第 k 小的數
2.1 nth_element的使用
思路
在強大的STL庫中存在一個神奇的函式,那就是nth_element,這個函式主要用來將陣列元素中第k小的整數排出來並在陣列中就位,隨時呼叫,可謂十分實用。
他的時間複雜的為O(n), 原因是他不要像排序那樣確定每個元素都在自己應該在的位置上,它只要保證指定的k位置左邊元素均小於它,右邊元素均大於它即可。
故實際實現使用分治演算法思想,只需要首尾指標對於陣列進行一次遞推遍歷即可,這裡先講nth_element函式的使用方法,在2.2中詳細介紹實現。
函式語句:nth_element(陣列名,陣列名+第k小元素,陣列名+元素個數)
程式碼
- 語言支援:C++
C++ Code:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
nth_element(nums.begin(), nums.begin() + n - k, nums.end());
return nums[n - k];
}
};
2.2 分治演算法
思路
我們使用分治思想,每次將整個區間分為左右兩個區域,選取一個基準點後,透過首尾指標不斷逼近,中間透過交換保證r左側的區間全部小於等於currNum, l右側的區間全部大於等於currNim
直到最後l > r結束迴圈,此時已經可以保證[begin, r] 和 [l, end]兩個區域都是"有序的",也就是左邊均小於等於currNum,右邊均大於等於currNum
我們再來判斷目標位置k所處的位置,分三種情況,每次都可以二分縮小一半查詢範圍:
1.k <= r, 去左半區間[begin, r]繼續尋找
2.k >= l, 去右半區間[l, end]繼續尋找
3.r < k < l. 這種情況比較特殊,具體可以見情況二圖片,必然是 l - r = 2, k = r + 1 = l - 1的情況,
否則如果像情況一,l - r = 1, 那麼必然有 k <= r 或者 k >= l, 而不可能存在r < k < l
大致的情況有以下兩種:
情況一:
情況二:
程式碼
class Solution {
public:
int ans = 0;
void sortNum(vector<int>& nums, int begin, int end, int k){
if(begin == end){
ans = nums[begin];
return;
}
int currNum = nums[begin + (end - begin) / 2];
int l = begin, r = end;
while(l <= r){
while(nums[l] < currNum) l++;
while(nums[r] > currNum) r--;
if(l <= r) swap(nums[l++], nums[r--]);
}
if(k <= r) sortNum(nums, begin, r, k);
else if(k >= l) sortNum(nums, l, end, k);
else sortNum(nums, r + 1, l - 1, k);
}
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
sortNum(nums, 0, n - 1, n - k);
return nums[n - k];
}
};