如何在 10 億數中找出前 1000 大的數

做者 | channingbreeze
來源公衆號 | 互聯網偵察
聲明:已獲做者受權轉載

小史是一個應屆生,雖然學的是電子專業,可是本身業餘時間看了不少互聯網與編程方面的書,一心想進BAT互聯網公司。面試





以前小史在 BAT 三家的面試中已經掛了兩家,今天小史去了 BAT 中的最後一家面試了。算法


簡單的自我介紹後,面試官給了小史一個問題。編程





【面試現場】數組





題目:如何在 10 億數中找出前 1000 大的數?bash
















小史:我能夠用分治法,這有點相似快排中 partition 的操做。隨機選一個數 t,而後對整個數組進行 partition ,會獲得兩部分,前一部分的數都大於 t ,後一部分的數都小於 t 。大數據





小史:若是說前一部分總數大於 1000 個,那就繼續在前一部分進行 partition 尋找。若是前一部分的數小於 1000 個,那就在後一部分再進行 partition ,尋找剩下的數。ui











小史:首先,partition 的過程,時間是 o(n)。咱們在進行第一次 partition 的時候須要花費 n ,第二次 partition 的時候,數據量減半了,因此只要花費 n/2,同理第三次的時候只要花費n/4,以此類推。而n + n/2 + n/4 + ...顯然是小於 2n 的,因此這個方法的漸進時間只有 o(n)spa



(注:這裏的時間複雜度計算只是簡化計算版,真正嚴謹的數學證實能夠參考算法導論相關分析。)3d








半分鐘過去了。code

















小史一時慌了神。




他回憶起了以前呂老師給他講解 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 的問題,知道分治法時間更少。可是碰到具體問題的時候仍是要具體分析,這種大數據量的狀況下反而用堆會更快。






本文做者是個人好朋友欣哥,原文連接以下:

【面試現場】如何在10億數中找出前1000大的數​
相關文章
相關標籤/搜索