这道题很强大,引出了很多知识点

题目

在未排序的数组中找到第 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 ≤ 数组的长度。

解答

题目要求找出一个序列中第K大元素,可以很容易想到下面的解法:

1,给序列排序,取出倒数第K个。快速排序为 n·log(n)

2,小顶堆,维护一个大小为K的小顶堆,把序列元素大于堆顶的元素依次入堆,完了堆顶就是第K大。维护一个堆(插入O(1),删除log(n))的时间复杂度是log(n),此处为log(k),总的时间复杂度是n·log(k),空间复杂度为O(k)。

PS:大顶堆也可以

3,快速选择算法,随时选择一个基准, 然后进行快排的partation过程(将序列中小于基准的都放在它的左边,大于它的都放在右边),基准归位,此时基准已经在序列中排好序的位置;再判断要找的第 N - k 个元素与基准坐标的关系, 如果k正好等于基准位置,那么数组第k小的数就是基准,如果K小于基准坐标位置,则只递归左半部分,否则只递归右半部分。

如果是快速排序算法,会在这里递归地对两部分进行快速排序,时间复杂度为n·log(n),而在这里,由于知道要找的第 N - k 小的元素在哪部分中,我们不需要对两部分都做处理,这样就将平均时间复杂度下降到O(n)。

这种算法最好情况是每次基准都划分在了序列中间位置,时间复杂度为O(n);最坏情况是每次基准都划分在了边缘位置,时间复杂度为O(n^2)。第四种方法优化了基准的选取,用线性复杂度O(n)的时间就解决了问题。

4,BFPRT, BFPRT算法就是在基准上做文章,能够保证每次所选的基准在数组的中间位置,那么时间复杂度就是O(N),BFPRT解法和快速选择解法唯一不同的就是在基准的选取上,所以只讲选取基准这一过程。

第一步:我们将数组每5个相邻的数分成一组,后面的数如果不够5个数也分成一组。

第二步:对于每组数,我们找出这5个数的中位数,将所有组的中位数构成一个median数组(中位数数组)。

第三步:我们再求这个中位数数组中的中位数,此时所求出的中位数就是基准。

第四步:通过这个基准进行partation过程,下面和常规解法就一样了。

BFPRT是专门用来求 TOP-K 问题的, 时间复杂度为O(N)。

通过代码如下:

1,排序

class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
return sorted(nums)[-k]

2,小顶堆

from heapq import *

class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
l = [] # 存储堆
for x in nums:
if l and len(l)==k and x>l[0]: # 堆满并且x大于堆顶,pop堆顶,x入堆
heapreplace(l, x)
if not l or len(l)<k: # 堆没满,直接入堆
heappush(l, x)
return l[0]
# 或者直接return nlargest(k, nums)[-1]

3,快速选择

import random

class Solution:
def findKthLargest(self, nums, k):
def partition(left, right, base):
temp = nums[base]
nums[base], nums[right] = nums[right], nums[base] # 基准和末尾元素互换 max_index = left
for i in range(left, right): # 把所有小于基准的移到左边
if nums[i] < temp:
nums[max_index], nums[i] = nums[i], nums[max_index]
max_index += 1 nums[right], nums[max_index] = nums[max_index], nums[right] # 基准归位
return max_index def select(left, right, k_smallest):
"""在 nums[left, right] 找第k小的元素"""
if left == right: # 递归终止条件
return nums[left]
pivot_index = random.randint(left, right) # 随机选择基准(比固定选第一个要好)
base_index = partition(left, right, pivot_index) # 选第一个(left)为基准,并归位。
if base_index == k_smallest: # 判断目前已归位的基准,是不是第k_smallest位
return nums[k_smallest]
elif k_smallest < base_index: # go to 左半部分
return select(left, base_index - 1, k_smallest)
else: # go to 右半部分
return select(base_index + 1, right, k_smallest) return select(0, len(nums) - 1, len(nums) - k) # 第k大,是第n-k小

4,BFPRT

class Solution:
def findKthLargest(self, nums, k):
def getmedian(lis):
"""返回序列lis中位数,在BFPRT中就是求每5个数小组的中位数"""
begin = 0
end = len(lis)-1 sum = begin+end
mid = sum//2 + sum % 2 # 这个地方加上sum%2是为了确保偶数个数时我们求的是中间两个数的后一个
return sorted(lis)[mid] def BFPRT(nums, left, right):
"""分成每5个数一个小组,并求出每个小组内的中位数"""
num = right-left+1
offset = 0 if num % 5 == 0 else 1 # 最后如果剩余的数不足5个,我们也将其分成一个小组,和前面同等对待
groups = num//5 + offset
median = [] # 中位数数组
for i in range(groups):
begin = left+i*5
end = begin + 4
Median = getmedian(nums[begin:min(end, right)+1])
median.append(Median)
return getmedian(median) def partition(nums, left, right, base):
"""在 nums[left, right] 将基准base归位"""
temp = nums[base]
nums[base], nums[right] = nums[right], nums[base] # 基准和末尾元素互换 max_index = left
for i in range(left, right): # 把所有小于基准的移到左边
if nums[i] <= temp: # 要等于啊!这里好坑的说.. 否则通不过样例[3, 3, 3, 3, 4, 3, 3, 3, 3] k = 1
nums[max_index], nums[i] = nums[i], nums[max_index]
max_index += 1
nums[right], nums[max_index] = nums[max_index], nums[right] # 基准归位
return max_index def select(nums, left, right, k_smallest):
"""在 nums[left, right] 找第k小的元素"""
if left == right: # 递归终止条件
return nums[left]
# pivot_index = random.randint(left, right)
base = BFPRT(nums, left, right)
base_index = partition(nums, left, right, nums.index(base)) # 选base为基准,并归位。
if base_index == k_smallest: # 判断目前已归位的基准,是不是第k_smallest位
return nums[k_smallest]
elif k_smallest < base_index: # 递归左半部分
return select(nums, left, base_index - 1, k_smallest)
else: # 递归右半部分
return select(nums, base_index + 1, right, k_smallest)
return select(nums, 0, len(nums) - 1, len(nums) - k) # 第k大,是第n-k小

BFPRT笔记 | 对于求包含重复值的序列第K大,应该先去重再入BFPRT。

PS:大顶堆也可以,写都写出来了,不贴出来怪怪的..

from heapq import *

class Solution:
# 用负数入堆建立大顶堆,pop()k次,就是第k大。时间复杂度最坏n·log(n),最好k·log(n),空间复杂度O(n)
def findKthLargest(self, nums: List[int], k: int) -> int:
l = []
for x in nums:
heappush(l, -x) # 平均O(1),最坏log(n)
for x in range(k):
c = heappop(l) # log(n)
return -c

最新文章

  1. lua upvalue
  2. [Android Pro] android 4.4 Android原生权限管理:AppOps
  3. linux中使用top获取进程的资源占用信息
  4. Remote 的远程使用
  5. Ajax发送和接收请求
  6. NOI2013矩阵游戏
  7. leetcode第四题:Median of Two Sorted Arrays (java)
  8. 一个用 Cumulative Penalty 培训 L1 正规 Log-linear 型号随机梯度下降
  9. spring mvc mybatis集成踩的坑
  10. 高通Android display架构分析
  11. NSTextField/NSTextView中显示超链接以及NSMutableAttributedString用法
  12. Laravel笔记--Eloquent 模型
  13. Linux下OSG的编译和安装以及遇到的问题
  14. 建立你第一个 Outlook Add-in
  15. c# 限制同时启动多个实例程序运行
  16. C语言 -- 字符串详解
  17. Redis的相关问题总结
  18. VUE系列三:实现跨域请求(fetch/axios/proxytable)
  19. HTML5+CSS3 表格设计(Table)
  20. 使用ssh向sqlserver2005数据库中保存image类型的二进制图片

热门文章

  1. Css if hack条件语法
  2. NOIP2016提高A组 B题 【HDU3072】【JZOJ4686】通讯
  3. PAT甲级——A1019 General Palindromic Number
  4. springmvc 串口读写 基于win7使用txrx netbeans jdk1.8 maven的
  5. Tensorboard在Win7下chrome无论如何无法连接的情况
  6. TZOJ 5986 玄武密码(AC自动机)
  7. leetcode 57 Insert Interval &amp; leetcode 1046 Last Stone Weight &amp; leetcode 1047 Remove All Adjacent Duplicates in String &amp; leetcode 56 Merge Interval
  8. 洛谷2593 [ZJOI2006]超级麻将——可行性dp
  9. 移动端适配(rem单位定义方法)
  10. 常用web字体的使用指南