LeetCode 热题 100_数组中的第K个最大元素(74_215)
题目描述:
给定整数数组 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
题解:
解题思路:
思路一(暴力破解法(将整个数组进行排序)):
1、先排序再取第k个最大元素
2、复杂度分析:
① 时间复杂度:O(n logn),n为数组中元素的个数,快速排序的时间复杂度为 O(n logn)。
② 空间复杂度:O(logn),主要为快速排序消耗的存储空间,取决于快排的递归深度。
思路二(线性时间选择(快速排序:基础版)):
1、具体思路如下:
① 假设数组中的第k个最大元素,其下标记作id。
② id在已排序元素的右侧,则继续对当前已经排序元素的右侧子区间进行查找(一次快排,确定一个元素位置,左侧元素小于排序好的元素,右侧元素大于排序好的元素)。
③ id在已排序元素的左侧,则继续对当前已经排序元素的左侧子区间进行查找。
④ 直至查找到第k个最大元素则返回
2、复杂度分析
① 时间复杂度:O(n),n为数组中元素的个数,因为快速选择算法的每次划分操作将数组分成两部分,大约一半的元素会被排除。
② 空间复杂度:O(logn),快排递归使用栈空间 O(logn)。
思路三(线性时间选择(快速排序:三路划分)):
1、具体思路如下:
① 假设数组中的第k个最大元素,其下标记作id。
② id在已排序元素的右侧,则继续对当前已经排序元素的右侧子区间进行查找(一次快排,确定一个元素位置,左侧元素小于排序好的元素,右侧元素大于排序好的元素)。
③ id在已排序元素的左侧,则继续对当前已经排序元素的左侧子区间进行查找。
④ 直至查找到第k个最大元素则返回
这里和思路二基本相同,唯一的不同在于,将划分的区间分为三个:big、equal和small。
- big 存储比中枢元素大的数据
- equal 存储和中枢元素相等的数据
- small 存储比中枢元素小的数据
这样我们就可以通过big和small的大小,来判断第k个最大元素是否存在equal中(防止数据重复带来的反复查找)。
2、复杂度分析
① 时间复杂度:O(n) ,其中 n 为数组元素数量。
② 空间复杂度:O(logn) , 划分函数的平均递归深度为 O(logn) 。
思路三参考链接
代码实现
代码实现(思路一(暴力破解法(将整个数组进行排序))):
class Solution1 {
public:
int findKthLargest(vector<int>& nums, int k) {
//快排
sort(nums.begin(),nums.end());
return nums[nums.size()-k];
}
};
代码实现(思路二(线性时间选择(快速排序:基础版))):
class Solution2 {
private:
// 快速排序中的划分操作
int partition(vector<int> &nums, int left, int right) {
// 选择随机元素为枢纽
int pivotIndex = left+rand()%(right-left+1);
swap(nums[left],nums[pivotIndex]);
int pivot=nums[left];
// 在划分过程中进行交换,直到左指针和右指针相遇
while (left < right) {
// 移动右指针,直到找到一个小于枢轴的元素
while (left < right && nums[right] >= pivot) {
--right;
}
// 将右指针指向的元素放到左边
nums[left] = nums[right];
// 移动左指针,直到找到一个大于枢轴的元素
while (left < right && nums[left] <= pivot) {
++left;
}
// 将左指针指向的元素放到右边
nums[right] = nums[left];
}
// 将枢轴放到其正确的位置
nums[left] = pivot;
// 返回枢轴元素的最终位置
return left;
}
public:
// 找到数组中第k大的元素
int findKthLargest(vector<int>& nums, int k) {
// 计算第k大的元素对应的索引位置
int id = nums.size() - k;
// 初始化左指针和右指针
int left = 0, right = nums.size() - 1;
int position;
// 进行快速选择算法的迭代查找
while (true) {
// 调用partition函数进行划分,返回枢轴的最终位置
position = partition(nums, left, right);
// 如果id在枢轴的左侧,则只需要在左侧子数组查找
if (id < position) {
right = position - 1;
}
// 如果id在枢轴的右侧,则只需要在右侧子数组查找
else if (id > position) {
left = position + 1;
}
// 如果id正好是枢轴的位置,表示找到了第k大的元素,结束循环
else {
break;
}
}
// 返回找到的第k大的元素
return nums[position];
}
};
代码实现(思路三(线性时间选择(快速排序:三路划分))):
class Solution3{
private:
int quickSelect(vector<int> &nums,int k){
// 基于快排的快速选择
// 随机选择基准数字
int pivot=nums[rand() % nums.size()];
// 将大于等于小于的元素分别放入三个数组
vector<int> big,equal,small;
for(const int &i:nums){
if (i<pivot) small.emplace_back(i);
else if(i>pivot) big.emplace_back(i);
else equal.emplace_back(i);
}
// 第k大元素在big中, 递归划分(注意这是从后往前计数)
if (k<=big.size()){
return quickSelect(big,k);
}
// 第k大元素在small中, 递归划分
if (big.size()+equal.size() < k){
return quickSelect(small,k-big.size()-equal.size());
}
// 第k大元素在equal中, 返回p
return pivot;
}
public:
int findKthLargest(vector<int>& nums, int k) {
return quickSelect(nums, k);
}
};
以思路二为例进行调试
#include<iostream>
#include <vector>
#include<algorithm>
using namespace std;
class Solution2 {
private:
// 快速排序中的划分操作
int partition(vector<int> &nums, int left, int right) {
// 选择左边元素作为枢轴(pivot)
int pivot = nums[left];
// 在划分过程中进行交换,直到左指针和右指针相遇
while (left < right) {
// 移动右指针,直到找到一个小于枢轴的元素
while (left < right && nums[right] >= pivot) {
--right;
}
// 将右指针指向的元素放到左边
nums[left] = nums[right];
// 移动左指针,直到找到一个大于枢轴的元素
while (left < right && nums[left] <= pivot) {
++left;
}
// 将左指针指向的元素放到右边
nums[right] = nums[left];
}
// 将枢轴放到其正确的位置
nums[left] = pivot;
// 返回枢轴元素的最终位置
return left;
}
public:
// 找到数组中第k大的元素
int findKthLargest(vector<int>& nums, int k) {
// 计算第k大的元素对应的索引位置
int id = nums.size() - k;
// 初始化左指针和右指针
int left = 0, right = nums.size() - 1;
int position;
// 进行快速选择算法的迭代查找
while (true) {
// 调用partition函数进行划分,返回枢轴的最终位置
position = partition(nums, left, right);
// 如果id在枢轴的左侧,则只需要在左侧子数组查找
if (id < position) {
right = position - 1;
}
// 如果id在枢轴的右侧,则只需要在右侧子数组查找
else if (id > position) {
left = position + 1;
}
// 如果id正好是枢轴的位置,表示找到了第k大的元素,结束循环
else {
break;
}
}
// 返回找到的第k大的元素
return nums[position];
}
};
int main(int argc, char const *argv[])
{
vector<int> nums={3,2,1,5,6,4};
int k=2;
Solution2 s;
cout<<s.findKthLargest(nums,k);
return 0;
}
LeetCode 热题 100_数组中的第K个最大元素(74_215)原题链接
欢迎大家和我沟通交流(✿◠‿◠)