剑指offer-最小的K个数

题目描述

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

问题分析

  • 方法1:对数组先排序,然后取前K个数。时间复杂度:O(NlogN)
  • 方法2:利用冒泡排序每一趟都能找到最小的元素,然后走K趟便能找到最小的K个数。时间复杂度: O(K*N)
  • 方法3:建立一个容量为K大根堆,先将数组的前K个元素入堆,然后对于数组后面的元素,依次与大根堆的堆顶元素(也就是K个值中的最大值)进行比较,若小于堆顶元素,则将堆顶元素出堆,将当前元素入堆。这样,便会淘汰掉较大的元素,最终,堆中维持的便是最小的K个数字,最差情况下,每个元素都要入堆,时间复杂度:O(NlogK)
  • 方法4:因为 Partition 过程最终能返回 pivot 在数组中的位置 pIndex , pIndex 之前的所有元素都小于等于 pivot ,pIndex之后的所有元素都大于等于 pivot, 所以,如果 pivot能够处于 K - 1处,即 pIndex = K - 1,那么【0~pIndex】便是该数组中最小的K个数。
    问题来了,如何能使pivot处于 K - 1处,即 pIndex = K - 1呢?
    • 不断迭代,若pIndex 大于 K - 1,说明 【0~pIndex】中是该数组最小的pIndex + 1(大于 K)个数,已经大于K个了,无法确定最小的K个数,需要缩小范围,那么 end = pIndex - 1,对【begin~end】范围内重新确定pIndex。
    • 若pIndex 小于于 K - 1,说明 【0~pIndex】中是该数组最小的pIndex + 1(小于 K)个数,说明还不足K个最小的元素,需要继续在右边找,那么 begin = pIndex +1,对【begin~end】范围内重新确定pIndex.
    • 不断迭代,直至pIndex等于 K-1

经验方法:

  • java中的是用优先级队列 PriorityQueue,可以实现指定大小的大根堆或者小根堆。构造方法中若是不传入比较器,那么默认是小根堆。要想实现大根堆,必须传入比较器。比较器有以下几种传法
    • PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, Collections.reverseOrder());
    • 新建一个类,实现Comparable接口重写里面的 compare()方法,改变比较规则
    • 传入匿名内部类,和上一个类似
  • PriorityQueue有以下几个方法:add, remove,poll,peek
  • PriorityQueue 同样可以用 foreach,具体见实现
  • 若数组中无重复元素,利用 Partition过程可以找到第K大的数字,当然,用堆也可以实现
  • 该题是找最小的K个数,若找最大的K个数, 第K小的数,第K大的数,都类似思想
  • 注意该题与剑指offer-数组中出现次数超过一半的数字联系

代码实现

  • 方法1:
    public ArrayList<Integer> GetLeastNumbers_Solution2(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if (input == null || input.length == 0 || k > input.length || k <= 0) {
            return res;
        }
        //对数组排序
        Arrays.sort(input);
        //取前k个数
        for (int i = 0; i < k; i++) {
            res.add(input[i]);
        }
        return res;
    }
  • 方法2:
    public ArrayList<Integer> GetLeastNumbers_Solution3(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if (input == null || input.length == 0 || k > input.length || k <= 0) {
            return res;
        }
        //冒泡排序:只找出来前k个数即可
        for (int i = 0; i < k; i++) {
            for (int j = input.length - 2; j >= i; j--) {
                if (input[j] > input[j + 1]) {
                    int temp = input[j];
                    input[j] = input[j + 1];
                    input[j + 1] = temp;
                }
            }
        }
        for (int i = 0; i < k; i++) {
            res.add(input[i]);
        }
        return res;
    }
  • 方法3:
    public ArrayList<Integer> GetLeastNumbers_Solution1(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if (input == null || input.length == 0 || k > input.length || k <= 0) {
            return res;
        }
        //创建大根堆的方式1:通过传入Collections.reverseOrder()
        //PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, Collections.reverseOrder());
        //创建大根堆的方式2:传入自定义的类对象(该类继承Comparator接口,重写compare方法,实现了从大到小排序)
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new MaxHeapComparator());
        //先填满这个大根堆
        for (int i = 0; i < k; i++) {
            maxHeap.add(input[i]);
        }

        for (int i = k; i < input.length; i++) {
            //如果元素小于最大堆的堆顶元素(这k个值中的最大值),那么弹堆,将该元素进堆
            if (input[i] < maxHeap.peek()) {
                maxHeap.poll();
                maxHeap.add(input[i]);
            }
        }
        //将堆中元素装入list
       // res.addAll(maxHeap);

        for (Integer num : maxHeap) {
            res.add(num);
        }

        return res;
    }

    public class MaxHeapComparator implements Comparator<Integer> {
        //compare方法,返回-1时,排列顺序为o1,o2;返回1时,排列顺序为o2,o1
        public int compare(Integer o1, Integer o2) {
            /*
            if (o1 > o2) {
                return -1;
            }else {
                return 1;
            }
            */
            return o2.compareTo(o1);
        }
    }
  • 方法四:
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if (input == null || input.length == 0 || k > input.length || k <= 0) {
            return res;
        }
        //利用partition过程使pivot停在 k-1处,那么【0~k-1】便是最小的K个数
        int begin = 0;
        int end = input.length - 1;
        int pIndex = partition(input, begin, end);
        while (pIndex != k - 1 ) {
            if (pIndex > k - 1 ) {
                end = pIndex - 1;
                pIndex = partition(input, begin, end);
            }
            if (pIndex < k - 1) {
                begin = pIndex + 1;
                pIndex = partition(input, begin, end);
            }
        }
        for (int i = 0; i < k; i++) {
            res.add(input[i]);
        }
        return res;
    }

    public int partition(int[] arr, int begin, int end) {
        int pivot = arr[begin];
        int i = begin + 1;
        int j = end;
        while (true) {
            while (i <= end && arr[i] < pivot) {
                i++;
            }
            while (j >= begin + 1 && arr[j] > pivot) {
                j--;
            }
            if (i < j && i <= end && j >= begin + 1 ) {
                swap(arr,i,j);
                i++;
                j--;
            }else {
                break;
            }
        }
        swap(arr,begin,j);
        return j;
    }

    public void swap(int[] arr, int i ,int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值