經典面試問題: Top K 之 ---- 海量數據找出現次數最多或,不重復的。


作者:林冠宏 / 指尖下的幽靈

掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8

博客:http://www.cnblogs.com/linguanh/

GitHub : https://github.com/af913337456/

騰訊雲專欄: https://cloud.tencent.com/developer/user/1148436/activities


僅列舉一些解決方法,事實的解決方案是非常多的。

這些問題都是面臨着有如下的考慮:

  • 內存不足以放下所有的數。
  • 機器CPU的核數不夠。
  • ...

問這些問題的意義:

如果能把這些問題答好,必然是綜合計算機各方面的知識,從內存到數據結構甚至還涉及到硬件,方法面面。至此,我給它定位是,綜合考量一個程序員計算機基礎能力的面試題。


一,找出不重復的

2.5億正整數中找出不重復的整數。

思路一:

分治法 + HashMap (HashMap 不要局限在 Java 語言)

將 2.5 億個整數,分批操作,例如分成 250 萬一批,共100批次。每批使用循環遍歷一次,存入 HashMap<int1,int2> 里面,int1 對應這個數,int2 對應它出現的次數,沒出現就默認是 1 次。每操作完一批,就進行當前的 HashMap去重操作,讀出 int2 > 1 的,排除掉。接下來的批次,以此類推,得出 100,剩下的自然就是不重復的。

好了,我們現在來計算下上面這個方案的雙間復雜度,時間 & 空間

時間復雜度250W * 100輪 + 其它批次。對於多核機器,可以啟動線程操作。

空間復雜度:使用 int 來進行存每一個數,保證不溢出情況下,那么就是 --> Key + Value : (250W * 4字節,4Byte)/(1024*1024) ~ (Key + 9.5MB) 內存。

思路二:

位圖法 Bitmap(一個 bit 僅會是 0 或 1)

對於此題,我們可以設計每兩個 bit 位,標示一個數的出現情況。00表示沒有出現,01表示出現一次,10表示出現多次。2.5 億個正整數,首先我們要知道是正整數,我們就不需要考慮負數,也就是無符號,無符號的整形占四個字節

我們以這個為例子,開始計算位圖內存。

1B = 8b,4B = 32b,它可以表示的最大的整數是 2^32-1(不溢出),也就是說,我們需要 2^32-1 ~ 2^32來表示這2.5億個數。我們上面說了,每個狀態是兩個,那么總共就是2^32*2個位。

那么我們可以一次申請的 位圖 內存是:2^32*2 bit ,(2^32*2)/(1024*1024*8) = 1GB 即可。當然,我們也可以加上分治的思路,分批處理,不用直接用 1G,哈哈。

那么這樣做的情況下怎樣找到這個數呢?我舉個例子,例如我們此時讀入一個數是:6464對應的所在bit位是:64*2=128,也就是說第 127128 位共同標示了它的出現狀態。其他的以此類推。每當我們讀出一個數,我們就這樣去找到它對應的bit位,先讀出bit位的值,再做記錄,已經是01的,再次來到,那么就應該修改為10。最后的我們這樣得出結果:掃描整個位圖,如果是10的,就下標/2得出這個數。

二,找出出現次數最多的

第一題:找出一篇文章中,出現次數最多的單詞。

第二題:10億個正整數找出重復次數最多的100個整數。

思路一:

分治法 + HashMap

沒錯,分治法 + HashMap 這個方法就是可以用來處理很多 Top K問題的。

對於問題一,其實比較簡單,這道題也是我 2016 年騰訊第三輪技術面要求當場寫代碼的題目。我們可以先判斷,這篇文章可能很長,也可能很短,那么我們應該規定一個字數的標志,作為一批的字數限制,例如100個文字。每100個文字是一批的處理極限,我們先讀出100個,100以內的就直接全部讀出。讀出后,打散成字符串,例如英語文章它以空格和一些符號分割。使用split方法就可以打散。此時我們得出一個字符串數組String[] array,有了這個之后就可以參考 找出不重復 問題的解法。每批使用循環遍歷一次,存入 HashMap<String,Integer> 里面,string 對應這個數的字符串,Integer 對應它出現的次數,最后最大的自然就是出現次數最多的。下面直接給出個 Demo 函數

// LinGuanHong
public static void search(String limitText){
    String maxWord = "";
    int    maxTime = 0;
    String[] words = limitText.split(" |\\.|,");
    int length = words.length;
    HashMap<String,Integer> one = new HashMap<>();
    for(int j=0;j<length;j++){
        Integer number = one.get(words[j]);
        if(number != null){
            number = number + 1;
            /** 找到次數加 1    */
            one.put(words[j],number);
            if(maxTime < number){
                maxTime = number;
                maxWord = words[j];
            }
        }else{
            /** 沒找到,賦值 1  */
            one.put(words[j],1);
        }
    }
    System.out.println("maxTime is :"+maxTime+" ; maxWord is :"+maxWord);
}

第二題對應的 分治法 + HashMap

按照前面的案例,我們首先一樣是要把這十億個數分成很多份。例如 1000份,每份 10萬。然后使用 HashMap<int,int> 來統計。在每一次的統計中,我們可以找出最大的100個數,為什么只找10萬中的100個啊?因為我們有1000份,其它份里面的第二大可能是這份里最小的。這樣全部加起來都100*1000個數了。OK,在我們找出這100*1000個侯選數后,繼續分治處理,或者直接進行排序,如果直接排序就是10W個數。排序算法可以選快排等之類的,前100個就是結果。

思路二:

位圖法 Bitmap

第一題,略。不是純數字的,不建議采用位圖法

第二題:

有了 找出不重復的 的例子做基礎。我們此時直接知道這題的 正整數 最大也是只能到 2^32-1,對於這道題,我們不需要乘2,所以我們申請的內存大小也是512MB。這樣我們就能使用這個位圖把所有數都存進去。如果出現了一次,該bit位 = 1,沒有就是0。多次出現的話,我們就不能累加到bit位里面了,因為它最大就是1。這時候我們會發現,出現多次的話,是無法通過bit位進行累加記錄的。所以,此題也是不適合采用位圖法

實際操作(參考網上)

實際上,最優的解決方案應該是最符合實際設計需求的方案,在時間應用中,可能有足夠大的內存,那么直接將數據扔到內存中一次性處理即可,也可能機器有多個核,這樣可以采用多線程處理整個數據集。

下面針對不容的應用場景,分析了適合相應應用場景的解決方案。

  • 單機+單核+足夠大內存

      如果需要查找10億個查詢次(每個占8B)中出現頻率最高的10個,考慮到每個查詢詞占8B,則10億個查詢次所需的內存大約是10^9 * 8B=8GB內存。如果有這么大內存,直接在內存中對查詢次進行排序,順序遍歷找出10個出現頻率最大的即可。這種方法簡單快速,使用。然后,也可以先用HashMap求出每個詞出現的頻率,然后求出頻率最大的10個詞。
    
  • 單機+多核+足夠大內存

      這時可以直接在內存總使用Hash方法將數據划分成n個partition,每個partition交給一個線程處理,線程的處理邏輯同(1)類似,最后一個線程將結果歸並。
    
      該方法存在一個瓶頸會明顯影響效率,即數據傾斜。每個線程的處理速度可能不同,快的線程需要等待慢的線程,最終的處理速度取決於慢的線程。而針對此問題,解決的方法是,將數據划分成c×n個partition(c>1),每個線程處理完當前partition后主動取下一個partition繼續處理,知道所有數據處理完畢,最后由一個線程進行歸並。
    
  • 單機+單核+受限內存

      這種情況下,需要將原數據文件切割成一個一個小文件,如次啊用hash(x)%M,將原文件中的數據切割成M小文件,如果小文件仍大於內存大小,繼續采用Hash的方法對數據文件進行分割,知道每個小文件小於內存大小,這樣每個文件可放到內存中處理。采用(1)的方法依次處理每個小文件。
    
  • 多機+受限內存

      這種情況,為了合理利用多台機器的資源,可將數據分發到多台機器上,每台機器采用(3)中的策略解決本地的數據。可采用hash+socket方法進行數據分發。
    

其他的

例如問:XXXXX中找出最大的一個,最小的一個,最大的幾個,最小的幾個。這類的就可以使用分治法+最小堆/最大堆秒之。

完矣


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM