top K 问题

top K 问题

/**
 * 输入整数数组 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]
 *
 *
 * 来源:力扣(LeetCode)
 * 链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof
 * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
 */

1. 方法一,排序,时间复杂度 O(nlgk)

2. 利用最大堆

  • 遍历数组,前k个数字依次入队,构建一个最大堆,假设此时堆内就是前k个数字,且堆首是最大元素

  • 后面的数字和堆首元素比较,如果比堆首元素小,接着入队,否则看下一个数字

  • 遍历结束,堆内就是前k个数字

    import java.util.Arrays;
    import java.util.Collections;
    import java.util.PriorityQueue;
    
    
    public class Leeoff40 {
        public int[] getLeastNumbers(int[] arr, int k) {
             if(invalidePar(arr, k)) throw new IllegalArgumentException("参数错误");
             k = Math.min(arr.length, k); // 防止k超过数组长度
    
             // 最大堆,传入逆序迭代器
             PriorityQueue<Integer> priQue = new PriorityQueue<>(Collections.reverseOrder());
    
    
             // 前k个元素入堆
            for(int i = 0; i < k; ++i){
                priQue.add(arr[i]);
            }
    
            // k个元素以后直接选最大的元素入堆
            for(int j = k; j < arr.length; ++j){
                Integer maxValue = priQue.remove();
                priQue.add(Math.min(maxValue, arr[j]));
            }
    
            // 返回结果
            Integer[] res = priQue.toArray(new Integer[0]);
            return Arrays.stream(res).mapToInt(Integer::valueOf).toArray(); // 转成int数组
        }
    
        private boolean invalidePar(int[] arr, int k){
            return (arr == null) || (k <= 0) || arr.length == 0;
        }
    
        public static void main(String[] arg){
            Leeoff40 lee = new Leeoff40();
            System.out.println(Arrays.toString(lee.getLeastNumbers(new int[]{1,5,2}, 2)));
            System.out.println(Arrays.toString(lee.getLeastNumbers(new int[]{0,1,2,1}, 1)));
            System.out.println(Arrays.toString(lee.getLeastNumbers(new int[]{1,5,2}, 0)));
            System.out.println(Arrays.toString(lee.getLeastNumbers(new int[]{1,5,2}, 4)));
            System.out.println(Arrays.toString(lee.getLeastNumbers(null, 4)));
        }
    }
    
    

3. 利用快速排序中分治的思路O(n)

  • 利用快排找到中枢的下标m,分成前后两段

  • m < k ,则在后半段寻找k

  • m > k ,则在前半段寻找k

  • m == k , k已经找到

    import java.util.Arrays;
    import java.util.Random;
    
    
    public class LeeOff40 {
        private  static Random random = new Random();
        public int[] getLeastNumbers(int[] arr, int k) {
            int[] res = null;
            int piv;
            if(invalidePar(arr, k)) throw new IllegalArgumentException("参数错误");
            k = Math.min(arr.length, k); // 防止k超过数组长度
            piv = getPiv(arr, 0, arr.length-1);
            while (piv != k - 1){
                if(piv > k - 1)piv = getPiv(arr, 0, piv - 1);
                else if(piv < k - 1) piv = getPiv(arr, piv + 1, arr.length - 1);
            }
            res = new int[k];
            System.arraycopy(arr, 0, res, 0, k);
            return  res;
            }
    
        private boolean invalidePar(int[] arr, int k){
            return (arr == null) || (k <= 0) || arr.length == 0;
        }
    
        /**
         * 选定一个枢纽,一个数组分成两部分, 左边的比这个数小,右边的数比这个数大
         * @param arr
         * @param start
         * @param end
         * @return 这个枢纽的下标
         */
        private int getPiv(int[] arr, int start , int end){
            int index = start + random.nextInt(end-start+1); // 随机选择中枢
            swap(arr, start, index); // index位置的元素和start位置的元素交换顺序
            int origin = arr[start], leftP = start, rightP = end;
            while (leftP < rightP){
                // 从后往前扫描,找到一个小于origin的值
                while (leftP < rightP && arr[rightP] >= origin)--rightP;
                swap(arr, rightP, leftP);
                // 从前往后扫,找到一个大于origin的值
                while (leftP < rightP && arr[leftP] <= origin)++leftP;
                swap(arr, leftP, rightP);
            }
            return leftP;
        }
    
        private void swap(int[] arr, int start, int index){
            // arr[start] 和 arr[index] 交换, 适用与不同的两个数字
            /*arr[start] = arr[start] ^ arr[index];
            arr[index] = arr[start] ^ arr[index];
            arr[start] = arr[start] ^ arr[index];*/
    
            /*arr[start] = arr[start] + arr[index];
            arr[index] = arr[start] - arr[index];
            arr[index] = arr[start] - arr[index];*/
            int temp = arr[start];
            arr[start] = arr[index];
            arr[index] = temp;
        }
    
        public static void main(String[] arg){
            LeeOff40 lee = new LeeOff40();
            System.out.println(Arrays.toString(lee.getLeastNumbers(new int[]{3,2,1}, 2)));
            System.out.println(Arrays.toString(lee.getLeastNumbers(new int[]{0,1,2,1}, 1)));
            // System.out.println(Arrays.toString(lee.getLeastNumbers(new int[]{1,5,2}, 0)));
            // System.out.println(Arrays.toString(lee.getLeastNumbers(new int[]{1,5,2}, 4)));
            // System.out.println(Arrays.toString(lee.getLeastNumbers(null, 4)));
            //  System.out.println(Arrays.toString(lee.getLeastNumbers(new int[0], 4)));
        }
    }
    
    
    

注意的地方:

  • 第一种和第三种方法,都需要把数组全部加载到内存中,而第二种方法适合处理海量数据,依次从磁盘读取,且不会改变数组的顺序
  • 注意参数检查
posted @ 2020-10-22 16:53  FizzPu  阅读(64)  评论(0编辑  收藏  举报