leetcode—26.求前k大值与前k小值

引言

  这是一类面试中特别容易被问到的题目,这里采用两种方法二叉堆与快速排序,直接排序与冒泡k次这里就不多赘述了,虽然写法简单,但是时间复杂度过高!!!

1.求最小的k个数

剑指 Offer 40. 最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

思路一:二叉堆

思路:建立一个大顶堆,维持堆的大小为k,如果新入队后,堆的大小大于k,则与新入队元素与堆顶比较,将较大的数移除,这样就可以保证堆中的元素是全体元素中最小的k个,此时,堆顶元素为第k小的值。堆是最小的k个数,堆顶又是最大的,因此堆顶就是第k小的

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        # 思路是:利用python中的最小堆模拟最大堆,每一次与堆顶元素比较,如果比堆顶元素大,则弹出堆顶元素并添加当前元素
        # 数组
        if k == 0:
            return []
        heap = [-i for i in arr[:k]]
        # 构造二叉堆
        heapq.heapify(heap)

        # 对数组的剩余元素与堆顶元素进行比较
        for i in range(k,len(arr)):
            # 如果堆顶元素相反数比当前元素大
            if -heap[0] > arr[i]:
                heapq.heappop(heap)
                heapq.heappush(heap,-arr[i])
        ans = list(map(lambda x : -x,heap))
        return ans

时间复杂度:O(nlogk),其中 n 是数组 arr 的长度。由于大根堆实时维护前 k 小值,所以插入删除都是 O(logk) 的时间复杂度,最坏情况下数组里 n 个数都会插入,所以一共需要 O(nlogk) 的时间复杂度。
空间复杂度:O(k),因为大根堆里最多 k 个数。

思路二:快速排序

快速选择算法:
这个问题可以转化为求第k小的数,此时,前k个位置正是最小的k个数
1. 先按基准位置进行分区,分区左侧小于基准位置,分区右侧大于基准位置,返回基准位置的索引
2.索引与k-1进行比较,对剩余位置进行分区,直至索引等于k-1

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        # 快速选择算法:
        # 这个问题可以转化为求第k小的数,此时,前k个位置正是最小的k个数
        # 1. 先按基准位置进行分区,分区左侧小于基准位置,分区右侧大于基准位置,返回基准位置的索引
        # 2.索引与k-1进行比较,对剩余位置进行分区,直至索引等于k-1
        if k == 0:
            return []
        
        def partation(arr,begin,end):
            # 基准位置从左侧开始选取
            mid_value = arr[begin]
            while begin < end:
                # 从右侧寻找比基准值小的数,从左侧寻找比基准值大的数
                while begin < end and arr[end] >= mid_value:
                    end -= 1
                arr[begin] = arr[end]

                while begin < end and arr[begin] <= mid_value:
                    begin += 1
                arr[end] = arr[begin]

            # 循环结束时,begin=end
            arr[begin] = mid_value
            # 返回基准值索引
            return begin

        n = len(arr)
        begin = 0
        end = n - 1
        index = partation(arr,begin,end)
        # 求第k小元素
        while index != k-1:
            # 说明要对index左侧继续分区
            if index > k-1:
                end = index-1
                index = partation(arr,begin,end)
            else:
                begin = index + 1
                index = partation(arr,begin,end)
        # 循环结束时index = k-1
        return arr[:k]

时间复杂度:期望为 O ( n ) O(n) O(n)
最坏情况下的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。情况最差时,每次的划分点都是最大值或最小值,一共需要划分 n−1 次,而一次划分需要线性的时间复杂度,所以最坏情况下时间复杂度为 O ( n 2 ) O(n^2) O(n2)

2.求最大的k个数

思路一:二叉堆

使用最小堆,维持堆的大小为k,如果新入队后,堆的大小大于k,则与新入队元素与堆顶比较,将较小的数移除,这样就可以保证堆中的元素是全体元素中最大的k个

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        # 思路是:利用python中的最小堆模拟最大堆,每一次与堆顶元素比较,如果比堆顶元素大,则弹出堆顶元素并添加当前元素
        # 数组
        if k == 0:
            return []
        heap = [i for i in arr[:k]]
        # 构造二叉堆
        heapq.heapify(heap)

        # 对数组的剩余元素与堆顶元素进行比较
        for i in range(k,len(arr)):
            # 如果堆顶元素相反数比当前元素大
            if heap[0] < arr[i]:
                heapq.heappop(heap)
                heapq.heappush(heap,arr[i])
        
        return heap

时间复杂度:O(nlogk),其中 n 是数组 arr 的长度。由于小根堆实时维护前 k 大值,所以插入删除都是 O(logk) 的时间复杂度,最坏情况下数组里 n 个数都会插入,所以一共需要 O(nlogk) 的时间复杂度。
空间复杂度:O(k),因为大根堆里最多 k 个数。
思路二:快速排序

快速选择算法:
这个问题可以转化为求第k大的数,此时,后k个位置正是最大的k个数
1. 先按基准位置进行分区,分区左侧小于基准位置,分区右侧大于基准位置,返回基准位置的索引
2.索引与n-k进行比较,对剩余位置进行分区,直至索引等于n-k

#!usr/bin/env python
# -*- coding:utf-8 -*-
"""
@author: admin
@file: 快速选择.py
@time: 2021/05/23
@desc:
"""


def getLeastNumbers(arr, k):
    def partation(arr, begin, end):
        # 基准位置从左侧开始选取
        mid_value = arr[begin]
        while begin < end:
            # 从右侧寻找比基准值小的数,从左侧寻找比基准值大的数
            while begin < end and arr[end] >= mid_value:
                end -= 1
            arr[begin] = arr[end]

            while begin < end and arr[begin] <= mid_value:
                begin += 1
            arr[end] = arr[begin]

        # 循环结束时,begin=end
        arr[begin] = mid_value
        # 返回基准值索引
        return begin

    n = len(arr)
    begin = 0
    end = n - 1
    index = partation(arr, begin, end)
    # 求第k小元素
    while index != n-k:
        # 说明要对index左侧继续分区
        if index > n-k:
            end = index - 1
            index = partation(arr, begin, end)
        else:
            begin = index + 1
            index = partation(arr, begin, end)
    # 循环结束时index = n-k
    return arr[n-k:]


if __name__ == '__main__':
    # arr = [0,0,0,2,0,5]
    arr = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    k = 5
    result = getLeastNumbers(arr, k)
    print(result)

如果对您有帮助,麻烦点赞关注,这真的对我很重要!!!如果需要互关,请评论或者私信!
在这里插入图片描述


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值