LeetCode 热题 100_数组中的第K个最大元素(74_215_中等_C++)(堆)(暴力破解法(将整个数组进行排序);线性时间选择(快速排序:基础版);线性时间选择(快速排序:三路划分))

题目描述:

给定整数数组 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)原题链接
欢迎大家和我沟通交流(✿◠‿◠)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值