如何在 10 亿数中找出前 1000 大的数

2022-5-16 18:47| 发布者: Hocassian| 查看: 54| 评论: 0|原作者: 五分钟学算法微信公众号

摘要:

如何在 10 亿数中找出前 1000 大的数

五分钟学算法

点击蓝色“五分钟学算法”关注我哟

加个“星标”,一起学算法

作者 | channingbreeze

来源 | 互联网侦察


小史是一个应届生,虽然学的是电子专业,但是自己业余时间看了很多互联网与编程方面的书,一心想进BAT互联网公司。



之前小史在 BAT 三家的面试中已经挂了两家,今天小史去了 BAT 中的最后一家面试了。


简单的自我介绍后,面试官给了小史一个问题。



【面试现场】



题目:如何在 10 亿数中找出前 1000 大的数?


小史:我可以用分治法,这有点类似快排中 partition 的操作。随机选一个数 t,然后对整个数组进行 partition ,会得到两部分,前一部分的数都大于 ,后一部分的数都小于 

小史:如果说前一部分总数大于 1000 个,那就继续在前一部分进行 partition 寻找。如果前一部分的数小于 1000 个,那就在后一部分再进行 partition ,寻找剩下的数。

小史:首先,partition 的过程,时间是 o(n)。我们在进行第一次 partition 的时候需要花费 ,第二次 partition 的时候,数据量减半了,所以只要花费 n/2,同理第三次的时候只要花费n/4,以此类推。而n + n/2 + n/4 + ...显然是小于 2n 的,所以这个方法的渐进时间只有 o(n)

(注:这里的时间复杂度计算只是简化计算版,真正严谨的数学证明可以参考算法导论相关分析。)


半分钟过去了。



小史一时慌了神。


他回忆起了之前吕老师给他讲解 bitmap 时的一些细节。突然有了一个想法。



小史在纸上画了画。



理解了算法之后,小史的代码写起来也是非常快,不一会儿就写好了:

/**
 * @author xiaoshi on 2018/10/14.
 */

public class TopN {

    // 父节点
    private int parent(int n) {
        return (n - 1) / 2;
    }

    // 左孩子
    private int left(int n) {
        return 2 * n + 1;
    }

    // 右孩子
    private int right(int n) {
        return 2 * n + 2;
    }

    // 构建堆
    private void buildHeap(int n, int[] data) {
        for(int i = 1; i < n; i++) {
            int t = i;
            // 调整堆
            while(t != 0 && data[parent(t)] > data[t]) {
                int temp = data[t];
                data[t] = data[parent(t)];
                data[parent(t)] = temp;
                t = parent(t);
            }
        }
    }

    // 调整data[i]
    private void adjust(int i, int n, int[] data) {
        if(data[i] <= data[0]) {
            return;
        }
        // 置换堆顶
        int temp = data[i];
        data[i] = data[0];
        data[0] = temp;
        // 调整堆顶
        int t = 0;
        while( (left(t) < n && data[t] > data[left(t)])
            || (right(t) < n && data[t] > data[right(t)]) ) {
            if(right(t) < n && data[right(t)] < data[left(t)]) {
                // 右孩子更小,置换右孩子
                temp = data[t];
                data[t] = data[right(t)];
                data[right(t)] = temp;
                t = right(t);
            } else {
                // 否则置换左孩子
                temp = data[t];
                data[t] = data[left(t)];
                data[left(t)] = temp;
                t = left(t);
            }
        }
    }

    // 寻找topN,该方法改变data,将topN排到最前面
    public void findTopN(int n, int[] data) {
        // 先构建n个数的小顶堆
        buildHeap(n, data);
        // n往后的数进行调整
        for(int i = n; i < data.length; i++) {
            adjust(i, n, data);
        }
    }

    // 打印数组
    public void print(int[] data) {
        for(int i = 0; i < data.length; i++) {
            System.out.print(data[i] + " ");
        }
        System.out.println();
    }

}


面试官看了一下。



小史熟练地介绍起了自己的项目,由于准备充分,小史聊起来游刃有余。面试官问的几个问题也进行了详细的解释。


小史走后,面试官在系统中写下了面试评语:



【遇见吕老师】


小史回到学校哼着歌走在校园的路上,正好碰到吕老师。



小史把面试情况和吕老师说了一下。


小史:感悟还挺深的。虽然平时做过 topN 的问题,知道分治法时间更少。但是碰到具体问题的时候还是要具体分析,这种大数据量的情况下反而用堆会更快。




END

 原 创 热 文 推 荐 


毕业十年后,我忍不住出了一份程序员的高考试卷

一道腾讯面试题:厉害了我的杯

十大经典排序算法动画与解析,看我就够了!(配代码完全版)

这或许是东半球分析十大排序算法最好的一篇文章

面试官,我会写二分查找法!对,没有 bug 的那种!


你点的每个“在看”,我都认真当成了喜欢
    已同步到看一看

    发送中


    路过

    雷人

    握手

    鲜花

    鸡蛋

    最新评论

    返回顶部