最小的k个数(partition操作)

last modify

问题简述

输入整数数组 arr ,找出其中最小的 k 个数

剑指 Offer 40. 最小的k个数 - 力扣(LeetCode)

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

示例 1:
    输入:arr = [3,2,1], k = 2
    输出:[1,2] 或者 [2,1]
示例 2:
    输入:arr = [0,1,2,1], k = 1
    输出:[0]
 
限制:
    0 <= k <= arr.length <= 10000
    0 <= arr[i] <= 10000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路1:快排中的 partition 过程

  • 快排的过程:

    • partition 过程:以数组某个元素(一般取首元素)为基准数,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。

    • 递归地对左右部分执行 partition 过程,直至区域内的元素数量为 1;

  • 基于以上思想,当某次划分后基准数正好是第 k+1 小的数字,那么此时基准数左边的所有数字便是题目要求的最小的 k 个数。

Python
class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:

        def partition(lo, hi):  # [lo, hi]
            if lo >= hi: return

            p = arr[lo]  # 选取第一个位置为基准点
            l, r = lo, hi  # l 的初始位置应该在 lo,而不是 lo + 1
            # 假设初始化为 lo + 1,当 a[lo] 为最小值时,此时训处循环后 l == r == lo + 1,再交换 a[lo] 和 a[l] 就会出错

            while l < r:  # 退出循环时 l == r
                # 先移动 r,在移动 l,此时退出循环后 l 和 r 同时指向一个小于 p 的值;反之,如果用 a[hi] 初始化 p,就要先移动 l,在移动 r;

                # 从 r 开始,从右往左找到第一个 < p 的值,所以循环条件是 >=
                while l < r and arr[r] >= p: r -= 1
                # 从 l 开始,从左往右找到第一个 > p 的值,所以循环条件是 <=
                while l < r and arr[l] <= p: l += 1
                arr[l], arr[r] = arr[r], arr[l]
                
            arr[lo], arr[l] = arr[l], arr[lo]  # 将基准点移动到分界点

            if l < k: partition(l + 1, hi)
            if l > k: partition(lo, l - 1)

        partition(0, len(arr) - 1)
        return arr[:k]

思路2:堆(优先队列)

  • 写法1) 维护一个长度为 k 的大顶堆(第一个数最大),当下一个元素小于堆顶值,就更新堆(弹出堆顶,插入新值);

  • 写法2) 直接对整个数组构建一个小顶堆,然后循环弹出前 k 个值;

  • 注意写法1 的时间复杂度是 O(NlogK),而写法2 是 O(NlogN)

Python:写法1(使用库函数)
Python:写法2(使用库函数)

思路3:计数排序

  • 因为题目限制了 arr[i] 的范围,所以还可以使用计数排序,时间复杂度 O(N)

Python

Last updated