数组中前k大的数系列

1. 概述

给定一个数组array,大小为n,找到其中第k大的数或者前k大的数。这是一个电面高频题,有快排,堆排等多种思路来解题,本文章将从时间复杂度和空间复杂度,来介绍这些算法以及它们所适用的场景

2. 堆排

直观上理解找到第k大或者前k大的数,用的是最大堆,但实际上最小堆也是可以的,至于具体使用哪一种,则取决于问题的场景。一般来说,面试答的是最小堆(省空间)
2.1 最大堆
要找第k大的元素,那么heapify一个max heap,然后既然是要找第k大的元素,max heap顶端是最大的,第k大的元素就是从最底层向上的第k个元素,实现方法只要heapify后pop k次,pop所得到的数便是前k大的数,最后一次pop便是第k大的数。

import heapq
class Solution(object):
	def findKthLargest(self, nums, k):
	nums = [-num for num in nums]
	heapq.heapify(nums)
	res = float('inf')
	for _ in range(k):
		res = heapq.heappop(nums)
	return -res

首先解释下为什么要nums=[-num for num in nums].
因为Python的heapq库调用heapify的时候,用的永远是一个min heap,然后因为没有max heap的实现,便用min heap来模拟max heap的运算,最简单的就是将所有的数变成-num.

时间复杂度 O ( n + k l o g n ) O(n+klogn) O(n+klogn),heapify用了O(n),然后一共pop了k个元素,每个元素使用logn的时间复杂度,所以一共是O(n+klogn)
空间复杂度 O ( n ) O(n) O(n),用了整个数组空间进行堆化,因此空间复杂度是O(n)

2.2 最小堆

  • 初始化一个size为k的数组,这k个数为输入数组array的前k的数,然后对这个数组进行最小堆的heapify,得到min heap
  • array数组中剩下的n-k的数依次与min heap比较,只要这个数比堆顶的数要大,那么就把最小值pop出来,该值放入堆顶做siftdown;否则这个数直接作为出来的一个数,堆不变,这样保证了出来的n-k个数都是比min heap中的k的数要大的,那么min heap中的k个数便是最大的k个数
import heapq
class Solution(object):
	def findKthLargest(self, nums, k):
		min_heap = nums[:k]
		heapq.heapify(min_heap)
		for i in range(k, len(nums)):
			if nums[i] > min_heap[0]:
				heapq.heappop(min_heap)
				heapq.heappush(min_heap, nums[i])
		return min_heap[0]

时间复杂度: O ( k ) + O ( ( n − k ) ∗ l o g k ) O(k)+O((n-k)*logk) O(k)+O((nk)logk)
空间复杂度: O ( k ) O(k) O(k)

2.3 最小堆VS最大堆

时间复杂度空间复杂度
最大堆 O ( n + k l o g n ) O(n+klogn) O(n+klogn)O(n)
最小堆 O ( k ) + O ( ( n − k ) ∗ l o g k ) O(k)+O((n-k)*logk) O(k)+O((nk)logk) O ( k ) O(k) O(k)

时间复杂度上进行对比:

  • 如果考虑k无限接近于n,
    最大堆: O ( n + n l o g n ) ≈ O ( n l o g n ) O(n+nlogn) \approx O(nlogn) O(n+nlogn)O(nlogn)
    最小堆: O ( n + l o g k ) ≈ O ( n ) O(n+logk) \approx O(n) O(n+logk)O(n)
  • 如果考虑k=0.5
    最大堆: O ( n + n l o g n ) O(n+nlogn) O(n+nlogn)
    最小堆: O ( n + n l o g n ) O(n+nlogn) O(n+nlogn)
  • 如果考虑n无限大
    最大堆:O(constantn)
    最小堆:O(n
    logk)

3. 快排

class Solution(object):
    res = 0
    def findKthLargest(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        self.helper(nums, len(nums)-k+1, 0, len(nums)-1)
        return self.res

    def helper(self, nums, k, low, high):
        pvt_pos = self.partion(nums, low, high)
        if pvt_pos - low + 1 == k:
            self.res = nums[pvt_pos]
            return
        elif pvt_pos - low + 1 > k:
            self.helper(nums, k, low, pvt_pos-1)
        else:
            self.helper(nums, k-pvt_pos+low-1, pvt_pos+1, high)


    def partion(self, nums, low, high):
        pivot = nums[low]
        while low < high:
            while low < high and nums[high] >= pivot:
                high -= 1
            nums[low] = nums[high]
            while low < high and nums[low] <= pivot:
                low += 1
            nums[high] = nums[low]
        nums[low] = pivot
        return low

sol = Solution()
nums = [3,2,1,5,6,4]
print(sol.findKthLargest(nums, 2))
print(nums)

时间复杂度
快排查找确定轴心来划分数组
所以时间复杂度为
O = s u m ( N + N / 2 + N / 4 + N / 8 + . . . ) O = sum(N + N/2 + N/4 + N/8 + ...) O=sum(N+N/2+N/4+N/8+...)
等比数列求和
S n = a 1 ( 1 − q n ) / ( 1 − q ) S_n = a_1(1 - q^n) / (1 - q) Sn=a1(1qn)/(1q)
解得
O = 2 N = O ( n ) O = 2N=O(n) O=2N=O(n)
注意平均复杂度是 O ( n ) O(n) O(n),最坏情况下(比如数组基本有序,找最大的数partion选的轴是第一个数)还是 O ( n 2 ) O(n^2) O(n2)

4.BFPRT算法

BFPRT算法解决的问题十分经典,即从某n个元素的序列中选出第k大(第k小)的元素,通过巧妙的分 析,BFPRT可以保证在最坏情况下仍为线性时间复杂度。该算法的思想与快速排序思想相似,当然,为使得算法在最坏情况下,依然能达到o(n)的时间复杂 度,五位算法作者做了精妙的处理。

算法步骤:

  1. 将n个元素每5个一组,分成n/5(上界)组。

  2. 取出每一组的中位数,任意排序方法,比如插入排序。

  3. 递归的调用selection算法查找上一步中所有中位数的中位数,设为x,偶数个中位数的情况下设定为选取中间小的一个。

  4. 用x来分割数组,设小于等于x的个数为k,大于x的个数即为n-k。

  5. 若i==k,返回x;若i<k,在小于x的元素中递归查找第i小的元素;若i>k,在大于x的元素中递归查找第i-k小的元素。

终止条件:n=1时,返回的即是i小元素。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值