剑指offer_40_最小的 K 个数

Posted 小片清风

tags:

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

最小的 k 个数

题目链接https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/

题目内容:输入整数数组 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

题目解析

题目的意思简单明了。获取最小的前 k 个数。

题目解法

方法一:python 内置函数排序

时间复杂度 O(nlogn) python 内置函数时间复杂度点此查看

代码:

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

面试就别拿这个献丑了,知道python牛就得了

方法二:手动排序,取前 k 小。快排为宜

为什么用快排呢。就是在取前 k 小的时候,可以判断在第 k 小的值出现在左侧(中间值 - i + 1 > k)时,向左递归,出现在右侧(中间值pos - i + 1 < k), 向右递归。

时间复杂度最好是O(n), 最坏会退化到O(n^2)

代码:

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        def partition(arr, l, r):
            #选定中值
            pivotvalue = arr[l]
            lmark = l + 1
            rmark = r
            done = False

            while not done:
                while lmark <= rmark and arr[lmark] <= pivotvalue:
                    lmark += 1
                while rmark >= lmark and arr[rmark] >= pivotvalue:
                    rmark -= 1
                if rmark < lmark:
                    done = True
                else:
                    arr[lmark], arr[rmark] = arr[rmark], arr[lmark]

            arr[l], arr[rmark] = arr[rmark], arr[l]
            return rmark
        
        def quicksort(arr, l, r, k):
            if l > r:
                return 
            pos = partition(arr, l, r)
            num = pos - l + 1
            if k == num:
                return
            if k < num:
                quicksort(arr, l, pos - 1, k)
            else:
                quicksort(arr, pos+1, r, k - num)

        if k == 0:
            return []
        quicksort(arr, 0, len(arr) - 1, k)
        return arr[:k]

方法三:使用大顶堆

求前 k 个最小用大顶堆。为什么呢?因为当我们根据题意维护一个 K 大小的大顶堆时,向堆中添加元素时,当这个被添加元素小于堆顶值(也就是当前堆最大值)时,大顶堆会抛出最大值,将这个值插入大顶堆。这样,我们就得到了比堆顶小的 k - 1 个最小值。这样逐个插入元素时,最后得到的堆里存的就是最小的 k 个数。

时间复杂度O(nlogk), 因为我们只需要维护 K 大小的堆。

代码:

class HeapList(object):
    """大顶推"""
    def __init__(self):
        self.heaplist = [0]
        self.size = 0

    def buildHeap(self, alist):
        i = len(alist) // 2
        self.size = len(alist)
        self.heaplist += alist[:]
        while i > 0:
            self.percDown(i)
            i -= 1

    def percUp(self, i):
        while i // 2 > 0:
            if self.heaplist[i] > self.heaplist[i // 2]:
                self.heaplist[i], self.heaplist[i // 2] = self.heaplist[i // 2], self.heaplist[i]
            i //= 2

    def insert(self, k):
        self.heaplist.append(k)
        self.size += 1
        self.percUp(self.size)

    def maxChild(self, i):
        if i * 2 + 1 > self.size:
            return i * 2
        else:
            if self.heaplist[i * 2] > self.heaplist[i * 2 + 1]:
                return i * 2
            else:
                return i * 2 + 1

    def percDown(self, i):
        while i * 2 <= self.size:
            mc = self.maxChild(i)
            if self.heaplist[i] < self.heaplist[mc]:
                self.heaplist[i], self.heaplist[mc] = self.heaplist[mc], self.heaplist[i]
            i = mc

    def delMax(self):
        retval = self.heaplist[1]
        self.heaplist[1] = self.heaplist[self.size]
        self.size -= 1
        self.heaplist.pop()
        self.percDown(1)
        return retval


# 采用大顶堆的方式,制作容量为 k 的大顶堆,向堆中添加元素时,比堆顶值小,就弹出堆顶,并将此元素添加进堆。这就保证,最后遍历完成后,
# 我们获得了比堆顶小的 k-1 个最小值
# 时间复杂度 O(nlogK)  因为只维护 K 大小的堆
class Solution:
    def getLeastNumbers(self, arr, k):
        if k == 0:
            return []
        heaplist = HeapList()
        heaplist.buildHeap(arr[:k])
        for i in arr[k: ]:
            if i < heaplist.heaplist[1]:
                heaplist.delMax()
                heaplist.insert(i)
        return heaplist.heaplist[1:]

附录

这里附一个 小顶堆的实现。

class Heap:
    """二叉堆的实现"""
    def __init__(self):
        self.heapList = [0]   # 默认一个 0 做占位,使得根节点的索引在 1 上
        self.currentSize = 0    # 最大节点的索引位置

    def perUp(self, i):
        """将小节点逐步上升"""
        while i // 2 > 0:
            if self.heapList[i] < self.heapList[i // 2]:
                self.heapList[i], self.heapList[i // 2] = self.heapList[i // 2], self.heapList[i]
            i = i // 2

    def insert(self, k):
        """插入节点"""
        self.heapList.append(k)
        self.currentSize += 1
        self.perUp(self.currentSize)

    def minChild(self, i):
        """获取左右两个子节点里较小的那个子节点的索引"""
        if i * 2 + 1 > self.currentSize:  # 右子节点超出节点数量
            return i * 2
        else:
            if self.heapList[i * 2] < self.heapList[i * 2 + 1]:
                return i * 2
            else:
                return i * 2 + 1

    def perDown(self, i):
        """将节点下沉到合适位置"""
        while (i * 2) <= self.currentSize:  # 说明有子节点
            mc = self.minChild(i)
            if self.heapList[i] > self.heapList[mc]:
                self.heapList[i], self.heapList[mc] = self.heapList[mc], self.heapList[i]
            i = mc

    def delMin(self):
        """删除小节点"""
        retval = self.heapList[1]  # 删除索引位置为 1 的节点
        self.heapList[1] = self.heapList[self.currentSize]
        self.heapList.pop()
        self.currentSize -= 1
        self.perDown(1)
        return retval

    def buildHeap(self, alist):
        i = len(alist) // 2
        self.currentSize = len(alist)
        self.heapList += alist[:]
        while i > 0:
            self.perDown(i)
            i -= 1

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

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

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

剑指 Offer 40. 最小的k个数

剑指 Offer 40. 最小的k个数

剑指Offer40 最小的k个数

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