题解 | #最小的K个数#

最小的K个数

http://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf

最小的K个数

问题描述:给定一个数组,找出其中最小的K个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
0 <= k <= input.length <= 10000
0 <= input[i] <= 10000
示例1
输入:[4,5,1,6,2,7,3,8],4
返回值:[1,2,3,4]
说明:返回最小的4个数即可,返回[1,3,2,4]也可以
示例2
输入:[1],0
返回值:[]
示例3
输入:[0,1,2,1,2],3
返回值:[0,1,1]

方法一

思路分析

对于本题我们直接想到的是对整个数组进行排序,然后输出数组中的前K个数,因此尝试使用排序算法,而对于0 <= k <= input.length <= 10000的数量级下使用python中sort排序算法是最合适的。蒂姆排序(sort排序)结合使用了归并排序和插入排序,具有良好的性能此处我们直接使用python中的排序算法。

python核心代码

# -*- coding:utf-8 -*-
class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        if tinput==None&nbs***bsp;tinput==[]&nbs***bsp;k<0&nbs***bsp;k>len(tinput):#输入的数组为空或者K不符合条件的情况直接返回空
            return []
        tinput.sort()#对数组进行排序
        return tinput[:k]

复杂度分析

  • 时间复杂度:蒂姆排序的平均时间复杂度为
  • 空间复杂度:   空间复杂度为$O(n)$

方法二

思路分析

对于本题我们能直接想到的求解方法是先将整个数组进行排序,而后输出数组中的前K个数,但是对于较大数组的情况下,排序的时间复杂度会大大增加。因此我们想到先将数组中的前K个数构建大顶堆,此时大顶堆顶点处的值为这K个数里面最大的,而后遍历数组中余下的数,如果第i个数大于大顶堆顶点,则大于全部的K个数,直接跳过,否则,将大顶堆的顶点替换为第i个数,之后将更新后的tmp数组构建大顶堆,直到遍历完整个数组,分析到这里我们也许已经理解为什么开始处构建大顶堆的原因,这样大顶堆始终存储的数组中的最小的K个数。

图解


C++核心代码

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> tmp;
        int len =input.size();
        if (len <= 0 || len < k || k <= 0 )//如果K大于数组长度返回空
            return tmp;
        for (int i = 0; i < k; i++)
            tmp.push_back(input[i]);//将数组input中的前K个值放入数组tmp
        make_heap(tmp.begin(), tmp.end(), less<int>());//K个数构建大顶堆,顶点存储这K个数中最大的
        for(int i = k; i < len; i++)//遍历数组中余下的数
        {
            if (input[i] > tmp.front())//如果第i个数大于大顶堆顶点,则大于这K个数,直接跳过
                continue;
            else
            {
                tmp[0]=input[i];//否则,将大顶堆的顶点替换为第i个数
                make_heap(tmp.begin(), tmp.end(), less<int>());//更新后的tmp数组构建大顶堆
                
            }
        }
        sort_heap(tmp.begin(),tmp.end());
        return tmp;//大顶堆中的K个数即为input中的最小的K个数
        
    }
};

复杂度分析

  • 时间复杂度:构建一个容量个k的大顶堆时间复杂度为$O(logk)$,共需要更新n-k次,因此总的时间复杂度为$O(nlogk)$
  • 空间复杂度:  构建大顶堆借助一个数组,空间复杂度为$O(k)$

方法三

思路分析

      本题也可使用快速排序的方法,快速排序算法是一种典型的分治算法,思想非常简单,在待排序的数列中,我们首先要找一个数字作为基准数。一般选择第 1 个数字作为基准数。接下来我们需要把这个待排序的数列中小于基准数的元素移动到待排序的数列的左边,把大于基准数的元素移动到待排序的数列的右边。这时,左右两个分区的元素就相对有序了;接着把两个分区的元素分别按照上面两种方法继续对每个分区找出基准数,然后移动,直到各个分区只有一个数时为止。因为我们要找的只是最小的k个数,并不需要将所有的数都进行排序,只需要在每次排序完,如果基础数的坐标 i 恰好等于 k ,那么我们就可以确定当前前面k个数就是我们的解,这也是减小时间复杂度的一种办法。

核心代码

# -*- coding:utf-8 -*-
class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        if k > len(tinput) or k <= 0:
            return [] 
        start = 0
        end = len(tinput) - 1
        index = self.quickSort(tinput, start, end)#第一次排序位置确定的元素下标
        self.partition(tinput,index,k,start,end)
        return tinput[:k]#返回k小的元素
    
    def partition(self,tinput,index,k,start,end):
        while index != k-1:#只需要寻找到排序位置确定的元素下标小于k的情况
            if index > k-1:#第k小的元素在基准元素左侧
                end = index - 1
                index = self.quickSort(tinput, start, end)
            if index < k-1:#第k小的元素在基准元素右侧
                start = index + 1
                index = self.quickSort(tinput, start, end)
    
    def quickSort(self, tinput, start, end):#快排
        low = start
        high = end
        temp = tinput[start]#左边第一个元素作为基准元素
        while low < high:
            while low < high and tinput[high] >= temp:
                high -= 1
            tinput[low] = tinput[high]
            while low <high and tinput[low] < temp:
                low += 1
            tinput[high] = tinput[low]
        tinput[low] = temp#交换元素位置
        return low#返回排序位置确定的元素下标


空间复杂度

  • 时间复杂度:使用快速排序的时间复杂度为$O(n log n)$,然而我们只需要找到K个最小的数,并不需要将所有的数都进行排序,只需要在每次排序完,如果基础数的坐标 $i$恰好等于 $k$ ,那么我们就可以确定当前前面K个数就是我们的解,这K个数是无序的,每一次只处理一侧的数据,而temp = tinput[start]取左边第一个元素作为基准元素,最坏的时间复杂度仍是$O(n^2)$
  • 空间复杂度:不需要额外的空间,空间复杂度为$O(1)$


全部评论

相关推荐

昨天 08:10
已编辑
门头沟学院 Java
2.4&nbsp;一面2.6&nbsp;二面2.9&nbsp;三面(hr面)2.13&nbsp;oc1.15号收到面试电话那会就开始准备,因为一开始没底所以选择推迟一段时间面试,之后开始准备八股,准备实习可能会问的东西,这期间hot100过了有六七遍,真的是做吐了快,八股也是背了忘,忘了背,面经也看了很多,虽然最后用上的只有几道题,可是谁知道会问什么呢自从大二上开始学java以来,一开始做外卖,点评,学微服务,大二下五六月时,开始投简历,哎,投了一千份了无音讯,开始怀疑自己(虽然能力确实很一般),后来去到一家小小厂,但是并不能学到什么东西,而且很多东西都很不规范,没待多久便离开,大二暑假基本上摆烂很怀疑自己,大三上因为某些原因开始继续学,期间也受到一俩个中小厂的offer,不过学校不知道为啥又不允许中小厂实习只允许大厂加上待遇不太好所以也没去,感觉自己后端能力很一般,于是便打算转战测开,学习了一些比较简单的测试理论(没有很深入的学),然后十二月又开始继续投,java和测开都投,不过好像并没有几个面试,有点打击不过并没有放弃心里还是想争一口气,一月初因为学校事比较多加上考试便有几天没有继续投,10号放假后便继续,想着放假应该很多人辞职可能机会大一点,直到接到字节的面试,心里挺激动的,总算有大厂面试了,虽然很开心,但同时压力也很大,心里真的很想很想很想进,一面前几天晚上都睡不好觉,基本上都是二三点睡六七点醒了,好在幸运终于眷顾我一次了(可能是之前太痛了),一面三十几分钟结束,问的都不太难,而且面试官人挺好但是有些问题问的很刁钻问到了测试的一些思想并不是理论,我不太了解这方面,但是也会给我讲一讲他的理解,但是面完很伤心觉得自己要挂了。但是幸运的是一面过了(感谢面试官),两天后二面,问的同样不算难,手撕也比较简单,但也有一两个没答出来,面试官人很好并没有追问,因为是周五进行的二面,没有立即出结果,等到周一才通知到过了,很煎熬的两天,根本睡不好,好在下周一终于通知二面过了(感谢面试官),然后约第二天三面,听别的字节同学说hr面基本上是谈薪资了,但是我的并不是,hr还问了业务相关的问题,不过问的比较浅,hr还问我好像比较紧张,而且hr明确说了还要比较一下,我说我有几家的面试都拒了就在等字节的面试,三面完后就开始等结果,这几天干啥都没什么劲,等的好煎熬,终于13号下午接到了电话通知oc了,正式邮件也同时发了,接到以后真的不敢信,很激动但更重要的是可以松一口气了,可以安心的休息一下了终于可以带着个好消息过年了,找实习也可以稍微告一段落了,虽然本人很菜,但是感谢字节收留,成为忠诚的节孝子了因为问的比较简单,面经就挑几个记得的写一下一面:1.实习项目的难点说一下2.实习中用到了哪些测试方法3.针对抖音评论设计一下测试用例4.手撕:合并两个有序数组二面:1.为什么转测开2.线程进程区别,什么场景适合用哪个3.发送一个朋友圈,从发出到别人看到,从数据流转的角度说一下会经历哪些过程4.针对抖音刷到广告视频设计测试用例5.手撕:无重复字符的最长字串
牛客85811352...:测开问这么简单?
查看8道真题和解析
点赞 评论 收藏
分享
01-11 08:47
门头沟学院 Java
choumoduji...:读研的目的就是为了以最快的速度和最低的要求完成“学校”规定的毕业标准,而不是所谓课题组的要求
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务