最小的k个数

第二十八题:最小的k个数

 

题目描述

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

 

解析:

               此题属于topK问题,还有几种其他的问法,大量数据中取出最小的k个数(或者取出最大k个数)

表面上看使用排序可以解决这个问题,但是当数据量足够大的时候,此时还能单纯的使用排序解决这个问题么?

 

话说老王又到了一家公司面试,又到了跟面试官日常吹bi的时刻了。

面试官:简单的介绍一下自己吧!

老王:我叫老王,隔壁老王的老王。。。。平时对写代码非常的执着。很喜欢跟计算机打交道.等等.........

面试官:我大概了解了你的一些基本情况,内心想想自己的老婆(这小子竟然叫老王,还是隔壁老王.....我必须要让他知难而退)

咱们先写一道算法题吧,题目上面描述所示。

老王:看了看题,直接就跟面试官说先给这些数字排好序,再取出前4个就是要的结果啦!!!此时老王深邃的眼神凝视着面试官。

而面试官并没有回答他,而是说先手写一下吧。于是老王迅速的写下了下面的代码:

 

很黄很暴力实现代码如下,(平均)时间复杂度O(NlogN):

public ArrayList<Integer> GetLeastNumbers_Solution(ArrayList<Integer> list, int k) {
        ArrayList<Integer> result = new ArrayList();
        if (list == null || input.length <= 0 || k<=0 || list.size()< k){
            return list;
        }
        //排序
        Collections.sort(list);
        for (int i = 0; i < k; i++){
            result.add(list.get(i));
        }
        return result;
}

 

面试官:看了一下老王写的代码,便说:你这种解法虽然巧妙,但是时间复杂度还是很高啊O(n^2),能不能再改善一下呢?

老王:(老子就知道你会跟我说时间复杂度太高的问题,哼!看我的另一种写法)便回答面试官:啊!假装的在思考。。。

有了,我想到该怎么解决了。说完便又写起了风骚的代码。。。。

 

 

利用堆结构,建立最大堆k个,再将k到最后一个数字添加到堆中,与堆顶值进行比较,(比堆顶值小就交换)

这里使用的是优先级队列(PriorityQueue),下面链接详细介绍大顶堆,小顶堆,堆排序相关一系列

堆结构的详细介绍

 

基于堆的解法O(NlogK),空间复杂度:

public ArrayList<Integer> GetLeastNumbers_Solution2(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList<>();
        if (input == null || input.length <= 0 || k<=0 || input.length < k){
            return list;
        }
        //大顶堆
        Queue<Integer> heap = new PriorityQueue<>(k, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        for (int i = 0;i < input.length;i++){
           if (heap.size() < k){
               heap.add(input[i]);
           }else {
               if (input[i] < heap.peek()){
                   heap.remove();
                   heap.add(input[i]);
               }
           }
        }
        while (!heap.isEmpty()){
            list.add(heap.remove());
        }
        return list;

}

 

面试官:看了一下老王写的代码,便说:时间复杂度是低了,但是你使用了额外的空间堆,明显的空间换时间,如果你能不使用额外空间

那就很完美了,说完面试官很自信的低下了头,继续看着老王的简历

老王:若有所思的想了想(这人还真是个奇葩,我用空间换取了时间,现在又让我节省空间,那时间肯定要慢下来啊,于是老王就利用快排来解决)

写完下面的代码后,面试官便问老王你的代码时间复杂度是多少?

老王:T(N) = O(NlogN) 说完老王脸红了下来,自己确实节省了空间,但是时间上有花费了更多。。。。。

面试官:你知道BFPRT算法么?

BFPRT算法解决了这样一个问题:在时间复杂度O(N)内,从无序数组中找到第k小的数

1.《算法导论》

2.《程序员代码面试指南 IT企业算法与数据结构题目最优解》

这两本书中都有介绍,下面链接是2书中的详细介绍

BFPRT算法详细介绍

 

基于快排partition的改进解法 O(NlogN):

public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList<>();
        if (input == null || input.length <= 0 || k<=0 || input.length < k){
            return list;
        }
        int start = 0;
        int end = input.length-1;
        int index = partition(input,start,end);
        while (index != (k-1)){
            if (index > k-1){
                end = index -1;
                index = partition(input,start,end);
            }else {
                start = index+1;
                index = partition(input,start,end);
            }
        }
        for(int i=0;i<k;++i){
            list.add(input[i]);
        }

        return list;
}
//切分
private static int partition(int[] arr, int l, int r) {
    int less = l - 1;
    int more = r;
    while (l < more) {
        if (arr[l] < arr[r]) {
            swap(arr, ++less, l++);
        } else if (arr[l] > arr[r]) {
            swap(arr, --more, l);
        } else {
            l++;
        }
    }
    //最后将最大区的最后一个数换到最大区的第一个数
    swap(arr, more, r);
    return less+1;
}

 

扩展:本题的题目是在数据量非常小的数组中找出最大或者最小的k个数,如果在100万个数字中查找最大或者最小的100个呢?

显然第一种方法和第二种方法直接就被抛弃了,个人还是推荐使用大顶堆或小顶堆去解的,毕竟使用堆空间所占用的并不是很多,时间复杂度上也是比较低的O(NlogK),用少量的空间换取时间还是可取的方案

 

NowCoder(Online Coding, Please Click)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值