解决Top K 问题

一、快排思想解决top K问题

public class AlgorithmTest {

    //得到最小的k个数
    public int[] getLeastNumbers(int[] arr, int k) {
        if (arr == null || k <= 0 || arr.length < k) {
            return new int[0];
        }
        return quickSearch(arr, 0, arr.length - 1, k - 1);
    }

    /**
     * 使用快排思想解决topK问题
     *
     * @param arr   数组
     * @param left  左边界
     * @param right 右边界
     * @param k     我们要查找下标为k-1的数
     * @return
     */
    private int[] quickSearch(int[] arr, int left, int right, int k) {
        //每快排分割一次,找到排序后下标为j的元素,如果j恰好等于k就返回j以及j左边的所有书
        int j = partition(arr, left, right);
        if (j == k) {
            return Arrays.copyOf(arr, j + 1);
        }
        //否则根据下标j与k的大小关系来决定继续切分左段还是右段
        return k < j ? quickSearch(arr, left, j - 1, k) : quickSearch(arr, j + 1, right, k);
    }

    /**
     * 快排的切分,返回下标j,使得arr[j]左边的数都比它小,右边的数都比它大
     *
     * @param arr
     * @param left
     * @param right
     * @return
     */
    private int partition(int[] arr, int left, int right) {
        int v = arr[left];
        int i = left;
        int j = right + 1;
        while (true) {
            while (++i <= right && arr[i] < v) {
            }
            while (--j >= left && arr[j] > v) {
            }
            if (i >= j) {
                break;
            }
            int tmp = arr[j];
            arr[j] = arr[i];
            arr[i] = tmp;
        }
        arr[left] = arr[j];
        arr[j] = v;
        return j;
    }


    public static void main(String[] args) {

        int[] arr = {12, 4, 54, 34, 100, 89, 9, 0, 10, 111};

        AlgorithmTest test = new AlgorithmTest();
        int[] rt = test.getLeastNumbers(arr, 5);
        for (int i = 0; i < rt.length; i++) {
            System.out.print(rt[i] + " ");
        }
    }
}

总结:使用快排,每次遍历都是上一次的一半,时间复杂度为O(N).

(1)时间复杂度:

每次遍历都是上一次的一半,时间复杂度期望为O(N) ,最坏情况下的时间复杂度为 O(n^2)。情况最差时,每次的划分点都是最大值或最小值,一共需要划分 n - 1 次,而一次划分需要线性的时间复杂度,所以最坏情况下时间复杂度为 O(n^2)。

(2)空间复杂度:期望为 O(log n),递归调用的期望深度为 O(logn),每层需要的空间为 O(1),只有常数个变量。

最坏情况下的空间复杂度为 O(n)。最坏情况下需要划分 n 次,即 quickSearch 函数递归调用最深 n - 1层,而每层由于需要 O(1)的空间,所以一共需要 O(n)的空间复杂度。

二、大跟堆/小跟堆求topK问题


大跟堆求前K小问题:求前K小问题,只需要维护一个容量为K的大跟堆即可。每次poll出最大的数,剩下的就是前K小了。如果维护一个小跟堆,那么需要将所有的数放进堆中,时间复杂度就是ON(logN),就不是ON(logK)了。

小跟堆求前K大问题:求前K大问题,只需要维护一个容量为K的小跟堆即可。每次poll出最小的数,剩下的就是前K大了。如果维护一个大跟堆,那么需要将所有的数放进堆中,时间复杂度就是ON(logN),就不是ON(logK)了。


public class AlgorithmTest {

    public static void main(String[] args) {

        int[] arr = {12, 4, 54, 34, 100, 89, 9, 0, 10, 111};

        AlgorithmTest test = new AlgorithmTest();
        int[] rt = test.getLeastNumbers(arr, 5);
        for (int i = 0; i < rt.length; i++) {
            System.out.print(rt[i] + " ");
        }
    }

    //得到最小的k歌数
    //堆:经过排序的完全二叉树
    //大跟堆:根结点的键值是所有堆结点键值中的最大值  =》 求前k小
    //      Queue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
    //            @Override
    //            public int compare(Integer o1, Integer o2) {
    //                return o2 - o1;
    //            }
    //        });
    //小跟堆:根结点的键值是所有堆结点键值中的最小值  =》 求前k大
    //      Queue<Integer> minHeap = new PriorityQueue<>(k);
    public int[] getLeastNumbers(int[] arr, int k) {
        if (arr == null || k <= 0 || arr.length < k) {
            return new int[0];
        }

        //定义一个大跟堆,PriorityQueue默认是小跟堆,重写一下比较器
        Queue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });


        for (int num : arr) {
            if (maxHeap.size() < k) {
                maxHeap.offer(num);
            } else if (maxHeap.peek() > num) {
                maxHeap.poll();
                maxHeap.offer(num);
            }
        }

        int[] resultArr = new int[k];
        for (int i = 0; i < k && !maxHeap.isEmpty(); i++) {
            resultArr[i] = maxHeap.poll();
        }
        return resultArr;
    }
}

总结:

(1)Java中自带PriorityQueue,实现起来比较简单,时间复杂度为ON(logK),空间复杂度为O(K)。

(2)PriorityQueue中的一些方法说明:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值