网易16年研发实习生笔试题 - 寻找第K大

### 问题

有一个整数数组,请你根据快速排序的思路,找出数组中第K大的数。
给定一个整数数组a,同时给定它的大小n和要找的K(K在1到n之间),请返回第K大的数,保证答案存在。


输入样例

[1,3,5,2,2],5,3


输出样例

2


JavaCode

import org.junit.Test;

public class FindKth {
    public int findKth(int[] a, int n, int K) {
        return find(a, 0, n-1, K);
    }

    //递归寻找数组中第K大的元素
    private int find(int[] a, int low, int high, int K) {
        int pivot = partition(a, low, high);

        if(pivot + 1 < K)//中轴位置少于K个,在右子数组中继续查找
            return find(a, pivot+1, high, K);
        else if(pivot + 1 > K)//中轴位置大于K个,在左子数组中继续查找
            return find(a, low, pivot-1, K);
        else//中轴元素正好是第K大的元素
            return a[pivot];
    }

    //将数组划分为两部分,左边较大,右边较小
    private int partition(int[] a, int low, int high) {
        // 将数组首元素作为每一轮比较的基准
        int pivotValue = a[low];

        while (low < high) {
            // 从右往左扫描,直到遇到比基准元素小的元素
            while (low < high && a[high] <= pivotValue)
                --high;

            // 将右子数组中不合格的元素放到左边不合格元素的位置(原元素已经移走)
            a[low] = a[high];

            // 从左往右扫描,直到遇到比基准元素大的元素
            while (low < high && a[low] >= pivotValue)
                ++low;

            // 将左子数组中不合格的元素放到左边不合格元素的位置(原元素已经移走)
            a[high] = a[low];
        }

        // 将基准元素放到中间位置
        a[low] = pivotValue;

        // 返回数组的中轴位置
        return low;
    }

    @Test
    public void test() {
        int[] a = new int[]{1,2,3,3,4,5,6,6};
        int n=8;
        int k=6;
        System.out.println(findKth(a, n, k));
    }
}

说明

  • 数组的K-th问题可以等价于top-K问题,其最典型的解法之一就是利用快排进行“剪枝“,因为快排在每一轮排序之后,能够将数组分成左右两部分,且左边部分的所有元素都大于右边部分的所有元素(假设是降序排列),每次都可以直接砍掉一部分元素,从而在下一轮排序中不需要考虑那些被砍掉的元素了,大大地较少了不必要的元素比较和交换。另外,如果要求最小的K个元素,这也可以采用类似的做法。常见排序算法的介绍可以参考排序算法代码总结

  • 由于快排的效率是不确定的,其时间复杂度最理想情况下是 O(nlogn) ,而最差为O(n^2)。后来大牛们提出了BFPRT算法,通过精心设计的 pivot 选取方法来改进快排,使得在最坏情形下也能保证线性时间复杂度!具体介绍可以参考NOALGO博客

  • top-K问题另一种典型的解法是使用堆排序,可以始终维护一个K个节点的小根堆,K个节点就是当前已知的最大的K个元素,然后遍历数组中剩余的元素,如果大于根节点,则用这个元素替换掉根节点,再调整堆使其重新成为小根堆,这种解法的复杂度为O(nlogk),且适合于海量数据的外部排序。另外,top-K问题还可以采用部分选择排序等方法,具体的分析和讨论可以参考程序员编程艺术:第三章、寻找最小的k个数

  • 昨天的网易内推在线笔试题中有一道问答题就是考察的top-K问题。具体问题是:网易云音乐需要统计在3小时、1天、1周时间内被用户播放次数最多的K首歌曲,请设计相应的数据结构和数据库模型。这是一道非常不错的应用题,但我当时答得不太好,顺便再吐槽一下牛客网的考试系统!后来想想,我觉得这题不仅考察了top-K问题,还考察了基于top-K的应用拓展。对于最基础的每3小时的top-K歌曲榜单,可以采用上述的方式解决,对于1天的top-K歌曲榜单,我们可以直接基于已经统计好的当天的8张top-K榜单进行合并排序,也就是从8*K个中再选出前K个元素,这样数据量就少了很多了,但是需要注意这里的8张基础榜单中肯定会有重复元素,所以我们需要对不同时段榜单中的同名歌曲的播放次数先进行合并,这可以使用HashMap来实现;类似的,对于1周的top-K歌曲榜单,我们可以基于一周之内的7张top-K榜单进行合并排序。数据库设计方面,只需要每3小时统计一份完整的歌曲播放记录表,待排序之后得到一张top-K榜单并持久化到数据库中,这个时间段内完整的歌曲播放记录表就可以删除了,类似的,可以每天(周)统计、排序并记录一张当天(周)的top-K榜单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值