Bootstrap

LeetCode 215 - 数组中第k大的元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

解法一:内置排序(Python)

时间复杂度 O ( N l o g N ) \mathcal O(NlogN) O(NlogN)

参考:
Python3 List sort()方法
python sort函数内部实现原理

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        nums.sort(reverse=True);
        return nums[k-1]

解法二:最大堆(Python)

思路是创建一个大顶堆,将所有数组中的元素加入堆中,并保持堆的大小小于等于 k。这样,堆中就保留了前 k 个最大的元素。这样,堆顶的元素就是正确答案。
在这里插入图片描述
时间复杂度 : O ( N log ⁡ k ) \mathcal{O}(N \log k) O(Nlogk)
空间复杂度 : O ( k ) \mathcal{O}(k) O(k),用于存储堆元素。

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        return heapq.nlargest(k, nums)[-1]

heapq.nlargest(n, iterable, key=None)
从 iterable 所定义的数据集中返回前 n 个最大元素组成的列表。 如果提供了 key 则其应指定一个单参数的函数,用于从 iterable 的每个元素中提取比较键 (例如 key=str.lower)。 等价于: sorted(iterable, key=key, reverse=True)[:n]

C++ priority_queue 实现

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int, vector<int>, greater<int> > heap;
        for(int num:nums)
        {
            heap.push(num);
            if(heap.size()>k)
                heap.pop();
        }
        int res = heap.top();
        return res;
    }
};

解法三:快速选择(Python)

快速选择算法 的平均时间复杂度为 O ( N ) \mathcal{O}(N) O(N)。就像快速排序那样,本算法也是 Tony Hoare 发明的,因此也被称为 Hoare选择算法。

本方法大致上与快速排序相同。简便起见,注意到第 k 个最大元素也就是第 N - k 个最小元素,因此可以用第 N - k 小算法来解决本问题。

首先,我们选择一个枢轴,并在线性时间内定义其在排序数组中的位置。这可以通过 划分算法(partition algorithm)的帮助来完成。
(为了实现划分,沿着数组移动,将每个元素与枢轴进行比较,并将小于枢轴的所有元素移动到枢轴的左侧)

这样,在输出的数组中,枢轴达到其合适位置。所有小于枢轴的元素都在其左侧,所有大于或等于的元素都在其右侧。

这样,数组就被分成了两部分。如果是快速排序算法,会在这里递归地对两部分进行快速排序,时间复杂度为 O ( N log ⁡ N ) \mathcal{O}(N \log N) O(NlogN)

而在这里,由于知道要找的第 N - k 小的元素在哪部分中,我们不需要对两部分都做处理,这样就将平均时间复杂度下降到 O ( N ) \mathcal{O}(N) O(N)

最终的算法十分直接了当 :

  • 随机选择一个枢轴

  • 使用划分算法将枢轴放在数组中的合适位置 pos。将小于枢轴的元素移到左边,大于等于枢轴的元素移到右边

  • 比较 pos 和 N - k 以决定在哪边继续递归处理

在这里插入图片描述
时间复杂度 : 平均情况 O ( N ) \mathcal{O}(N) O(N),最坏情况 O ( N 2 ) \mathcal{O}(N^2) O(N2)
空间复杂度 : O ( 1 ) \mathcal{O}(1) O(1)

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        def partition(left, right, pivot_index):
            pivot = nums[pivot_index]
            # move pivot to end 
            nums[pivot_index], nums[right] = nums[right], nums[pivot_index]
            # move all smaller elements to the left
            store_index = left
            for i in range(left, right):
                if nums[i] < pivot:
                    nums[store_index], nums[i] = nums[i], nums[store_index]
                    store_index += 1
            # move pivot to its final place 
            nums[right], nums[store_index] = nums[store_index], nums[right]

            return store_index

        def select(left, right, k_smallest):
            if left == right: return nums[left] # if the list only have one element

            # select a random pivot_index
            pivot_index = random.randint(left, right)
            # find the pivot position
            pivot_index = partition(left, right, pivot_index)
            # the pivot is in its final sorted position
            if k_smallest == pivot_index:
                return nums[pivot_index]
            # go left
            elif k_smallest < pivot_index:
                return select(left, pivot_index - 1, k_smallest)
            else:
                return select(pivot_index+1, right, k_smallest)
        
        return select(0, len(nums) - 1, len(nums)-k)

partition algorithm 演示
在这里插入图片描述

;