《剑指offer》【最小的k个数】(python版)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_20141867/article/details/81152894

问题描述:

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

思路:

这个算是面试中常见题了,这里总结一下解决方法(注意判断k的合法性):

1、全部升序排序,输出前k个数字,最小时间复杂度为O(nlog^n)

2、由于不要求最小的k个数按序输出,参考快排中partition函数思想:每轮排序之后枢轴左边数字都比它小,右边数字都比它大。因此如果排序之后枢轴刚好处于数组的第k个位置,那么此时数组的前k个数字也即是最小的k个数

这种思想经证明是O(n)的时间复杂度(有兴趣的可以参考《算法导论》)

代码实现如下:

# -*- coding:utf-8 -*-
class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        n = len(tinput)
        if k <= 0 or k > n:
            return list()
        start = 0
        end = n - 1
        mid = self.partition(tinput, start, end)
        while k - 1 != mid:
            if k - 1 > mid:
                start = mid + 1
                mid = self.partition(tinput, start, end)
            elif k - 1 < mid:
                end = mid - 1
                mid = self.partition(tinput, start, end)
        res = tinput[:mid+1]
        # res.sort()
        return res
        
    def partition(self, numbers, low, high):
        key = numbers[low]
        while low < high:
            while low < high and numbers[high] >= key:
                high -= 1
            numbers[low] = numbers[high]
            while low < high and numbers[low] <= key:
                low += 1
            numbers[high] = numbers[low]
        numbers[low] = key
        return low

3、一个很常见的例子,假设我们要选5个最矮的同学参加比赛,候选人选好之后又跑来一个新同学参加评选。这个时候新同学只需要PK候选人中最高的,如果他比最高的还高,就被淘汰,如果比最高的矮,他就可以替代这个最高的。按照这个思想,无论再来多少新同学都可以很快速的选择。

以这个思想为基础,为了最快的找到候选人中最高的,以身高为基准建立大顶堆,每次新人与堆顶PK,PK结束之后维护候选人重新为大顶堆。

这种思想将时间复杂度降到以k为基数,每次调整堆的时间复杂度都是O(log^k),那么整体时间复杂度为O(klog^{k})+O((n-k)log^k) = O(nlog^k)

实现代码如下:

class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        n = len(tinput)
        if k <= 0 or k > n:
            return list()
        # 建立大顶堆
        for i in range(int(k / 2) - 1, -1, -1):
            self.heapAjust(tinput, i, k - 1)
        for i in range(k, n):
            if tinput[i] < tinput[0]:
                tinput[0], tinput[i] = tinput[i], tinput[0]
                # 调整前k个数
                self.heapAjust(tinput, 0, k - 1)
        print(tinput[:k])

    def heapAjust(self, nums, start, end):
        temp = nums[start]
        # 记录较大的那个孩子下标
        child = 2 * start + 1
        while child <= end:
            # 比较左右孩子,记录较大的那个
            if child + 1 <= end and nums[child] < nums[child + 1]:
                # 如果右孩子比较大,下标往右移
                child += 1
            # 如果根已经比左右孩子都大了,直接退出
            if temp >= nums[child]:
                break
            # 如果根小于某个孩子,将较大值提到根位置
            nums[start] = nums[child]
            # nums[start], nums[child] = nums[child], nums[start]
            # 接着比较被降下去是否符合要求,此时的根下标为原来被换上去的那个孩子下标
            start = child
            # 孩子下标也要下降一层
            child = child * 2 + 1
        # 最后将一开始的根值放入合适的位置(如果前面是交换,这句就不要)
        nums[start] = temp

值得一提的是,最后一种方法将全部排序降为部分排序,这在海量数据中查找topK中是很有用的,当数据是增量式,或者无法全部加载进内存中时,只开辟一小部分空间存储k个数字还是可以实现的。

猜你喜欢

转载自blog.csdn.net/qq_20141867/article/details/81152894
今日推荐