返回最小的前K个数

题目:返回最小的前K个数

注:一千个人心理有一千个哈姆雷特。哪怕思路一致,1000个人可能写出的代码都会有1000种形式,在此我只是分享下思路,至于代码实现,各位看客参考即可,如果有更好的思路,欢迎在评论区

问题描述:

给定一个数组,找出其中最小的K个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。如果K>数组的长度,那么返回一个空的数组

输入:

[4,5,1,6,2,7,3,8],4 

返回值:

[1,2,3,4]

问题分析:

​ 首先看到这个题目,我们应该可以想到,如果想要返回最小的前k个数,之前有类似的题目是返回最小的第K个数,看似和这个题相似,但是其中还有点区别。这道题我们需要拿到k前面的k-1个数,也就是排完序之后的arr[0]---->arr[k-1]。处理这个问题的时候,有两种思维方式,第一种是将数组全部排完序,然后一次性得到结果;第二种是每次拿一个数,并且保证每次拿的数是剩余元素中最小的。然后拿k次,最后返回结果。两种方式没有好坏之分,一次性拿需要全局排序,一劳永逸;分批次拿每次都会浪费时间,所以具体看实际情况应用即可。

1)克隆数组比较法:

​ 最直接可以想到的方法就是,题目所给数组假设为a,我们新建一个数组b,然后遍历a的元素,在插入b的时候我们加入比较规则,这样当a的元素全部插入到b的时候,那么b就是有序数组。我们直接返回前k个元素即可

2)优先队列取头法:

​ 第一个方法我们需要手动实现比较规则,既然如此,我们可以使用java自带的优先队列(优先队列保证队列的第一个元素是极大/极小值,不保证全局有序)。我们可以把数组a的元素全部插入队列,在插入的同时,队列会自动调整队列头元素的值(类似于堆中的调整堆过程)。

​ 等全部插入之后,依次从队列中取出元素(取出后也会有一个调整的过程来保证队列头为极大/极小)。

​ 取k次,然后将取到的数据返回即可

3)快排区间定位法:

​ 众所周知,快排是一种将散列的数组高效规整的算法之一。数组越乱,快排越有奇效。

​ 此处,我们使用快排是因为快排采用分而治之的思想,每次都是类似一半一半处理,在此题中,我们恰好也不需要全局有序,只需要前k个数有序即可。所以这里我们可以使用快排。

​ 当快排的哨兵位置就是k的时候,我们直接返回0–k的元素即可。(“哨兵”是快排中的“中间值”,max[哨兵左元素们]<=value[哨兵]<=min[哨兵右元素们])

代码实现:

1)克隆数组比较法

​ 该算法实现较为简易,此处暂先略过

2)优先队列取头法:

/**
     * 优先队列法
     * 描述:优先队列允许传入元素按照定义的大小顺序存储
     * 思路:遍历给定数组,将元素依次插入到队列,之后从队列中依次取出前k个数
     *
     * 总结:
     * 1)PriorityQueue类似于大小顶堆,只能保证第一个元素是最大或者最小,不保证除此之外其他元素的大小关系。所以在debug期间可能会看到queue的内部顺序是乱的,但是第一个元素始终是极大或者极小。
     * 2)当调用了.poll()之后,内部会重新排序,同时也会保证第一个元素是有序的。
     * 3)初始化的时候可以自定义排序方式,jkd 1.8支持lambda表达式作为类传参。
     */
    public ArrayList<Integer> Solution_01(int[] input,int k){
    
    
 
 
        if(k>input.length){
    
    
            return new ArrayList<>();
        }
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>((x,y)->x-y);
        for(int i:input){
    
    
            queue.add(i);
        }
        ArrayList<Integer> res = new ArrayList<>();
        for(int i=0;i<k;i++){
    
    
            res.add(queue.poll());
        }
        return res;
    }

3)快排区间定位法:

/**
     * 快排找数法
     * 描述:快排是一种将一组杂乱无序元素快速整理的算法。"哨兵"分割的元素,max(左边)<= "哨兵" <= min(右边)
     * 思路:按照快排的方式,如果"哨兵" = k,那么直接返回哨兵左边的k个数据即可
     *
     * @param input,K
     */
    public ArrayList<Integer> Solution_02(int[] input,int k){
    
    
 
        QSort(input,0,input.length-1,k);
        ArrayList<Integer> res = new ArrayList<>();
        for(int i=0;i<k;i++){
    
    
            res.add(input[i]);
        }
        return res;
    }
 
    public void QSort(int[] arr,int low,int high,int k){
    
    
 
        if(low>=high) return;
        int left = low;
        int right = high;
        int refer = arr[low];//基准
        while(low<high){
    
    
            while(refer<=arr[high] && low<high){
    
    
                high--;
            }
            while(refer>=arr[low] && low<high){
    
    
                low++;
            }
            //此刻的arr[high]<refer   arr[low]>refer 交换顺序
            if (low<high){
    
    
                int temp = arr[high];
                arr[high] = arr[low];
                arr[low] = temp;
            }
        }
        //最终交换基准和low/high的值
        arr[left] = arr[low];
        arr[low] = refer;
 
        if(k == low){
    
    
            return ;
        }
        if(low > k){
    
    
            QSort(arr,left,low-1,k);
        }else{
    
    
            QSort(arr,low+1,right,k);
        }
    }

猜你喜欢

转载自blog.csdn.net/weixin_41998764/article/details/117486479