剑指 Offer 40. 最小的k个数

Posted 炫云云

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了剑指 Offer 40. 最小的k个数相关的知识,希望对你有一定的参考价值。

剑指 Offer 40. 最小的k个数

输入整数数组 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

排序

本题使用排序算法解决最直观,对数组 arr 执行排序,再返回前 k 个元素即可。使用任意排序算法皆可,本文采用并介绍 快速排序 ,为下文 方法二 做铺垫。

快速排序原理:

  • 快速排序算法有两个核心点,分别为 “哨兵划分” 和 “递归” 。
  • 哨兵划分操作: 以数组某个元素(一般选取首元素)为 基准数 ,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。

如下图所示,为哨兵划分操作流程。通过一轮 哨兵划分 ,可将数组排序问题拆分为 两个较短数组的排序问题 (本文称之为左(右)子数组)。

递归:左子数组右子数组 递归执行 哨兵划分,直至子数组长度为 1 时终止递归,即可完成对整个数组的排序。

如下图所示,为示例数组 [2,4,1,0,3,5] 的快速排序流程。观察发现,快速排序和 二分法 的原理类似,都是以 log ⁡ \\log log 时间复杂度实现搜索区间缩小。

class Solution(object):
    def getLeastNumbers(self, arr, k):
        """
        :type arr: List[int]
        :type k: int
        :rtype: List[int]
        """
        def quick_sort(arr, l, r):
            # 子数组长度为 1 时终止递归
            if l >= r: 
                return
            # 哨兵划分操作(以 arr[l] 作为基准数
            i, j = l, r
            while i < j:
                while i <j and arr[j] >= arr[l]:
                    j-=1
                while i <j and  arr[i] <= arr[l]:
                    i +=1
                arr[i], arr[j] = arr[j], arr[i] # arr[j] < arr[l] <arr[i]时候
            arr[l], arr[i] = arr[i], arr[l] # 交换哨兵
            # 递归左(右)子数组执行哨兵划分
            quick_sort(arr,l,i-1)
            quick_sort(arr,i+1,r)

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

基于快速排序的数组划分

题目只要求返回最小的 k 个数,对这 k 个数的顺序并没有要求。因此,只需要将数组划分为 最小的 k 个数其他数字 两部分即可,而快速排序的哨兵划分可完成此目标。

根据快速排序原理,如果某次哨兵划分后 基准数正好是第 k + 1 k+1 k+1​ 小的数字 ,那么此时基准数左边的所有数字便是题目所求的 最小的 k k k 个数 。

根据此思路,考虑在每次哨兵划分后,判断基准数在数组中的索引是否等于 k k k ,若 true 则直接返回此时数组的前 k 个数字即可。

算法流程:

getLeastNumbers() 函数:

  1. k k k 大于数组长度,则直接返回整个数组;
  2. 执行并返回 quick_sort() 即可;

quick_sort() 函数:

注意,此时 quick_sort() 的功能不是排序整个数组,而是搜索并返回最小的 k k k 个数。

  1. 哨兵划分:
  • 划分完毕后,基准数为 arr[i] ,左 / 右子数组区间分别为 [l,i−1] , [i + 1, r]
  1. 递归或返回
  • k < i k < i k<i ,代表第 k + 1 k + 1 k+1​ 小的数字在 左子数组 中,则递归左子数组;
  • k > i k > i k>i ,代表第 k + 1 k + 1 k+1​小的数字在 右子数组 中,则递归右子数组;
  • k = i k = i k=i,代表此时 a r r [ k ] arr[k] arr[k] 即为第 k + 1 k + 1 k+1 小的数字,则直接返回数组前 k k k 个数字即可;

class Solution(object):
    def getLeastNumbers(self, arr, k):
        """
        :type arr: List[int]
        :type k: int
        :rtype: List[int]
        """
        if k >= len(arr): 
            return arr
        def quick_sort(l, r):
            i , j =l,r
            while i <j:
                while i <j and arr[j] >= arr[l]:
                    j-=1
                while i <j and  arr[i] <= arr[l]:
                    i +=1
                arr[i], arr[j] = arr[j], arr[i]
            arr[l], arr[i] = arr[i], arr[l]
            # i 为哨兵的索引
            if k < i: return quick_sort(l, i - 1) 
            if k > i: return quick_sort(i + 1, r)
            return arr[:k]
            
        return quick_sort(0, len(arr) - 1)


参考

Krahets - 力扣(LeetCode) (leetcode-cn.com)

以上是关于剑指 Offer 40. 最小的k个数的主要内容,如果未能解决你的问题,请参考以下文章

剑指offer--40最小的k个数

剑指 Offer 40. 最小的k个数

剑指 Offer 40. 最小的k个数

剑指Offer40 最小的k个数

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

剑指Offer-时间效率面试题40:最小的k个数