快速选择
用于求解 Kth Element 问题,也就是第 K 个元素的问题。
可以使用快速排序的 partition() 进行实现。需要先打乱数组,否则最坏情况下时间复杂度为 O(N2)。
堆
用于求解 TopK Elements 问题,也就是 K 个最小元素的问题。使用最小堆来实现 TopK 问题,最小堆使用大顶堆来实现,大顶堆的堆顶元素为当前堆的最大元素。实现过程:不断地往大顶堆中插入新元素,当堆中元素的数量大于 k 时,移除堆顶元素,也就是当前堆中最大的元素,剩下的元素都为当前添加过的元素中最小的 K 个元素。插入和移除堆顶元素的时间复杂度都为 log2N。
堆也可以用于求解 Kth Element 问题,得到了大小为 K 的最小堆之后,因为使用了大顶堆来实现,因此堆顶元素就是第 K 大的元素。
快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。
可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。
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
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
题解
Topk问题( JavaScript-Algorithms ),不过于三种方案:
排序,取第 k 个
构造前 k 个最大元素小顶堆,取堆顶
计数排序或桶排序,但它们都要求输入的数据必须是有确定范围的整数,所以本题不可用
那么除了这两种方案还有没有其它的方式可解决本题喃?其实还有两种:
快速选择(quickselect)算法
中位数的中位数(bfprt)算法
(1)首先,这是自己想的一种方法,看这道题的第一想法就是这个——利用js中数组的sort()方法,但是刚开始写的时候,就直接使用了sort函数,然后返回数组中第k大的数,也就是数组从前到后索引为数组长度减去k的数,然而,我忘记了一点,就是sort()函数的性质,sort()函数是会按照数字的字符串形式进行排序,也就是并不是真正意义上的数值排序,根据红宝书中写到的,如果利用sort()函数对数值进行排序,则需要在sort()函数中加入一个compare()函数的参数,固定格式如下(降序则反之)。如我在网上查到的关于sort(compare)的真正含义,即此方法使用Array.sort(compareFunction和sortOptions)顺序的语法和参数,其参数定义如下: compareFunction - 用于确定数组元素排序顺序的比较函数。此参数是可选的。比较函数应该用于比较两个参数。给定元素的A和B,compareFunction的结果可以是负值,0或正值: 如果返回值为负,则表示A在排序序列中出现在B之前。 如果返回值为0,则A和B具有相同的排序顺序。 如果返回值为正,则表示A在排序序列中出现在B之后。如以上内容不理解,则建议反复查看红宝书P149页。
1 | var findKthLargest = function(nums, k) { |
简化一下可以这样。
1 | var findKthLargest = function(nums, k) { |
复杂度分析:
- 时间复杂度:O(nlogn)
- 空间复杂度:O(logn) 目前还不知道这个是怎么算出来的,先背下来吧。
(2)构造前 k 个最大元素小顶堆,取堆顶
我们也可以通过构造一个前 k 个最大元素小顶堆来解决,小顶堆上的任意节点值都必须小于等于其左右子节点值,即堆顶是最小值。
所以我们可以从数组中取出 k 个元素构造一个小顶堆,然后将其余元素与小顶堆对比,如果大于堆顶则替换堆顶,然后堆化,所有元素遍历完成后,堆中的堆顶即为第 k 个最大值。
遍历数组需要 O(N) 的时间复杂度,一次堆化需要 O(logK) 时间复杂度,所以利用堆求 Top K 问题的时间复杂度为 O(NlogK)。
具体步骤如下:
从数组中取前 k 个数( 0 到 k-1 位),构造一个小顶堆
从 k 位开始遍历数组,每一个数据都和小顶堆的堆顶元素进行比较,如果小于堆顶元素,则不做任何处理,继续遍历下一元素;如果大于堆顶元素,则将这个元素替换掉堆顶元素,然后再堆化成一个小顶堆。
遍历完成后,堆顶的数据就是第 K 大的数据
解析详情参考力扣大牛分析
1 | let findKthLargest = function(nums, k) { |
(3)快速选择算法
每执行一次的时候,比较基准值位置是否在 n-k 位置上,如果小于 n-k ,则第 k 个最大值在基准值的右边,我们只需递归基准值右边的子序列即可;如果大于 n-k ,则第 k 个最大值在基准值的做边,我们只需递归基准值左边的子序列即可;如果等于 n-k ,则第 k 个最大值就是基准值。
1 | let findKthLargest = function(nums, k) { |
Author: Amanda-Zhang
Link: http://chunchunya.github.io/2021/01/27/1.27_%E6%8E%92%E5%BA%8F/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.