这是一类面试中特别容易被问到的题目,这里采用两种方法二叉堆与快速排序,直接排序与冒泡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)。
思路一:二叉堆
使用最小堆,维持堆的大小为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)
如果对您有帮助,麻烦点赞关注,这真的对我很重要!!!如果需要互关,请评论或者私信!