力扣-215. 数组中的第K个最大元素

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

2.3 小根堆

思路

由于建堆和更新堆时间复杂度都是O(n),我们考虑使用小根堆
思路很简单,我们总是维护一个大小为k的小根堆,遍历数组的同时不断更新堆,保证小根堆中是当前遍历到的元素中最大的k个
而因为是小根堆,所以根节点值是这k个元素中最小的,也就是说,在遍历完数组所有元素后,小根堆的根节点值就是我们要求的第K大的元素值!

代码

class Solution {
public:
    void heapadjust(vector<int>& heap, int curindex, int len) {
        // 这里下标从0开始,所以是要+1
        int child = curindex * 2 + 1;
        int curValue = heap[curindex];
        while (child < len) {
            // 判断是否有右子节点,且选择更小的那个
            if (child + 1 < len && heap[child] > heap[child + 1]) {
                child++;
            }
            // 如果父节点比左右子节点较小的那个更大的话,进行交换
            // if (heap[curindex] > heap[child]) {
            if (curValue > heap[child]) {
                heap[curindex] = heap[child];
                curindex = child;
                child = curindex * 2 + 1;
            }
            // 否则说明排序已经正确,退出循环
            else {
                break;
            }
        }
        heap[curindex] = curValue;
    }

    int findKthLargest(vector<int>& nums, int k) {
        if (nums.size() < k)
            return -1;
        // 开始初始化参数(这里其实实际分配[0, k-1]共k个元素,end()是到达不了的!!!!)
        vector<int> heap(nums.begin(), nums.begin() + k);
        int len = nums.size(), sublen = heap.size();
        // 自底向上初始化小根堆(小根堆这里从下标0开始,所以注意-1)
        // sublen/2-1代表的是最后一个有叶子结点的父节点(完全二叉树)
        for (int i = sublen / 2 - 1; i >= 0; i--) {
            heapadjust(heap, i, sublen);
        }
        // 开始更新维护小根堆
        for (int j = k; j < len; j++) {
            if (nums[j] <= heap[0])
                continue;
            else {
                heap[0] = nums[j];
                heapadjust(heap, 0, sublen);
            }
        }
        return heap[0];
    }
};

2.4 优先级队列

思路

对于2.3,如果不想手动建立一个小根堆,我们可以考虑使用一个一小根堆为基础的优先级队列

代码

#include <vector>
#include <queue>
#include <functional>

class Solution {
public:
    int findKthLargest(std::vector<int>& nums, int k) {
        if (nums.size() < k) {
            return -1;
        }

        // 创建一个小根堆(greater<int》,父元素 > 子元素 下沉,即小根堆)
        std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;

        // 构建初始大小为k的小根堆
        for (int i = 0; i < k; ++i) {
            minHeap.push(nums[i]);
        }

        // 维护小根堆
        for (int i = k; i < nums.size(); ++i) {
            if (nums[i] > minHeap.top()) {
                minHeap.pop();
                minHeap.push(nums[i]);
            }
        }

        // 堆顶元素就是第k大的元素
        return minHeap.top();
    }
};
posted @ 2024-06-20 23:55  DawnTraveler  阅读(10)  评论(0编辑  收藏  举报