[230529 lc215] 数组中的第K个最大元素
又是一道伤心的题。是上个月旷视一面,面试官让我用快排写这道题,当时脑子很懵,不知道怎么写快排。遗憾离场。
一 题目
给定整数数组 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
二 快速排序原始版
思路:利用快速排序获得升序数组,然后返回 nums[size - k]
快速排序的实现:传入一个乱序数组,选中最右边的元素为主元,在剩余元素中使用双指针的做法,i 指针扫过的值均为小于主元的值,当 j 指针遍历完剩余元素后,交换 i 位置和主元位置上的元素,此时 pivot 就位,然后递归地调用 quickSort 函数对左右两边的子数组进行快排。
代码实现:
class Solution {
private:
void quickSort(vector<int>& nums, int left, int right) {
if(right <= left) {
return;
}
int pivot = right;
int i = left;
for(int j = left; j < right; ++j) {
if(nums[j] < nums[pivot]) {
swap(nums[i], nums[j]);
++i;
}
}
swap(nums[i], nums[pivot]);
quickSort(nums, left, i - 1);
quickSort(nums, i + 1, right);
}
public:
int findKthLargest(vector<int>& nums, int k) {
int size = nums.size();
quickSort(nums, 0, size - 1);
return nums[size - k];
}
};
三 快速排序优化版
思路:在上面的思路中,是递归地调用 quickSort 使整个数组有序。实际上我们只需要在每趟 quickSort 之后比较主元的下标与 size - k 的大小,然后更新 left 或者 right,对主元左边或者右边的子数组进行 quickSort 就可以了。
快排的实现:quickSort 函数返回快排后主元的下标
代码实现:
class Solution {
private:
int quickSort(vector<int>& nums, int left, int right) {
if(right <= left) {
return left;
}
int pivot = right;
int i = left;
for(int j = left; j < right; ++j) {
if(nums[j] < nums[pivot]) {
swap(nums[i], nums[j]);
++i;
}
}
swap(nums[i], nums[pivot]);
return i;
}
public:
int findKthLargest(vector<int>& nums, int k) {
int size = nums.size();
int left = 0, right = size - 1;
while(true) {
int ret = quickSort(nums, left, right);
if(ret == size - k) {
return nums[ret];
} else if(ret < size - k) {
left = ret + 1;
} else {
right = ret - 1;
}
}
return -1;
}
};
四 C++ 的优先级队列实现堆排序
思路:把所有元素压入大顶堆中,再逐个弹出元素,直至弹出第 k 个元素。或者维护一个 size 始终为 k 的小顶堆,最后返回堆顶元素。以下为第二种实现。
代码实现:
class Solution {
private:
class Less{
public:
bool operator()(int a, int b) {
return a > b;
}
};
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, Less> que;
int size = nums.size();
for(int i = 0; i < size; ++i) {
que.push(nums[i]);
if(i >= k) {
que.pop();
}
}
return que.top();
}
};
五 手撕堆排序
思路:通过大顶堆堆排序逐元素从大到小构造升序序列,当构造到第 k 大个元素时返回其值。
堆排序的思路:先把原数组初始化为一个大顶堆,此时数组中第一个元素即最大元素,然后交换最大元素和最后一个元素的位置,接着调整前 size - 1 个元素为大顶堆,找到第二大的元素,交换位置。如此迭代,直到找到第 k 大个元素。
代码实现:
class Solution {
private:
void heapify(vector<int>& nums, int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if(left < n && nums[left] > nums[largest]) {
largest = left;
}
if(right < n && nums[right] > nums[largest]) {
largest = right;
}
if(largest != i) {
swap(nums[i], nums[largest]);
heapify(nums, n, largest);
}
}
public:
int findKthLargest(vector<int>& nums, int k) {
int size = nums.size();
for(int i = size / 2 - 1; i >= 0; --i) {
heapify(nums, size, i);
}
for(int i = size - 1; i >= size - k; --i) {
swap(nums[0], nums[i]); //此时nums[i]就序
heapify(nums, i, 0);
}
return nums[size - k];
}
};