吐血整理 -- 10道精選 BAT 海量數據面試題,看完不虛了

http://www.jianshu.com/p/6ad90028dcbc面試


前言

你們想學習更多面試技巧,技術乾貨,能夠加一下個人公衆號:碼農富哥(搜索coder2025)

算法

掃碼關注個人公衆號:碼農富哥

面試題目

通過對BAT常考的海量數據題的收集,整理出下面BAT的高頻面試題,看無缺好理解完,之後面試不會虛。如下是嘔血整理, 先看看題目數組

  • 如何從大量的 URL 中找出相同的 URL?(百度)
  • 如何從大量數據中找出高頻詞?(百度)
  • 如何找出某一天訪問百度網站最多的 IP?(百度)
  • 如何在大量的數據中找出不重複的整數?(百度)
  • 如何在大量的數據中判斷一個數是否存在?(騰訊)
  • 如何查詢最熱門的查詢串?(騰訊)
  • 如何統計不一樣電話號碼的個數?(百度)
  • 如何從 5 億個數中找出中位數?(百度)
  • 如何按照 query 的頻度排序?(百度)
  • 如何找出排名前 500 的數?(騰訊)

答案呢?往下看~函數

題目1

題目描述
給定 a、b 兩個文件,各存放 50 億個 URL,每一個 URL 各佔 64B,內存限制是 4G。請找出 a、b 兩個文件共同的 URL。
學習

解答思路
每一個 URL 佔 64B,那麼 50 億個 URL佔用的空間大小約爲 320GB。
網站

5,000,000,000 * 64B ≈ 5GB * 64 = 320GB
因爲內存大小隻有 4G,所以,咱們不可能一次性把全部 URL 加載到內存中處理。對於這種類型的題目,通常採用分治策略,即:把一個文件中的 URL 按照某個特徵劃分爲多個小文件,使得每一個小文件大小不超過 4G,這樣就能夠把這個小文件讀到內存中進行處理了。
搜索引擎

思路以下:spa

首先遍歷文件 a,對遍歷到的 URL 求 hash(URL) % 1000,根據計算結果把遍歷到的 URL 存儲到文件 a0, a1, a2, ..., a999,這樣每一個大小約爲 300MB。使用一樣的方法遍歷文件 b,把文件 b 中的 URL 分別存儲到文件 b0, b1, b2, ..., b999 中。這樣處理事後,全部可能相同的 URL 都在對應的小文件中,即 a0 對應 b0, ..., a999 對應 b999,不對應的小文件不可能有相同的 URL。那麼接下來,咱們只須要求出這 1000 對小文件中相同的 URL 就行了。指針

接着遍歷 ai( i∈[0,999]),把 URL 存儲到一個 HashSet 集合中。而後遍歷 bi 中每一個 URL,看在 HashSet 集合中是否存在,若存在,說明這就是共同的 URL,能夠把這個 URL 保存到一個單獨的文件中。日誌

方法總結
1.分而治之,進行哈希取餘;2.對每一個子文件進行 HashSet 統計。

題目2

題目描述
有一個 1GB 大小的文件,文件裏每一行是一個詞,每一個詞的大小不超過 16B,內存大小限制是 1MB,要求返回頻數最高的 100 個詞(Top 100)。

解答思路
因爲內存限制,咱們依然沒法直接將大文件的全部詞一次讀到內存中。所以,一樣能夠採用分治策略,把一個大文件分解成多個小文件,保證每一個文件的大小小於 1MB,進而直接將單個小文件讀取到內存中進行處理。

思路以下:

首先遍歷大文件,對遍歷到的每一個詞x,執行 hash(x) % 5000,將結果爲 i 的詞存放到文件 ai 中。遍歷結束後,咱們能夠獲得 5000 個小文件。每一個小文件的大小爲 200KB 左右。若是有的小文件大小仍然超過 1MB,則採用一樣的方式繼續進行分解。

接着統計每一個小文件中出現頻數最高的 100 個詞。最簡單的方式是使用 HashMap 來實現。其中 key 爲詞,value 爲該詞出現的頻率。具體方法是:對於遍歷到的詞 x,若是在 map 中不存在,則執行 map.put(x, 1);若存在,則執行 map.put(x, map.get(x)+1),將該詞頻數加 1。

上面咱們統計了每一個小文件單詞出現的頻數。接下來,咱們能夠經過維護一個小頂堆來找出全部詞中出現頻數最高的 100 個。具體方法是:依次遍歷每一個小文件,構建一個小頂堆,堆大小爲 100。若是遍歷到的詞的出現次數大於堆頂詞的出現次數,則用新詞替換堆頂的詞,而後從新調整爲小頂堆,遍歷結束後,小頂堆上的詞就是出現頻數最高的 100 個詞。

方法總結
1.分而治之,進行哈希取餘;
2.使用 HashMap 統計頻數;
3.求解最大的 TopN 個,用小頂堆;
4.求解最小的 TopN 個,用大頂堆。



題目3

題目描述:
現有海量日誌數據保存在一個超大文件中,該文件沒法直接讀入內存,要求從中提取某天訪問百度次數最多的那個 IP。

解答思路
這道題只關心某一天訪問百度最多的 IP,所以,能夠首先對文件進行一次遍歷,把這一天訪問百度 IP 的相關信息記錄到一個單獨的大文件中。接下來採用的方法與上一題同樣,大體就是先對 IP 進行哈希映射,接着使用 HashMap 統計重複 IP 的次數,最後計算出重複次數最多的 IP。

注:這裏只須要找出出現次數最多的 IP,能夠沒必要使用堆,直接用一個變量 max 便可。
方法總結

  • 分而治之,進行哈希取餘;
  • 使用 HashMap 統計頻數;
  • 求解最大的 TopN 個,用小頂堆;
  • 求解最小的 TopN 個,用大頂堆。

題目4

題目描述
在 2.5 億個整數中找出不重複的整數。注意:內存不足以容納這 2.5 億個整數。

解答思路
方法一:分治法
與前面的題目方法相似,先將 2.5 億個數劃分到多個小文件,用 HashSet/HashMap 找出每一個小文件中不重複的整數,再合併每一個子結果,即爲最終結果。

方法二:位圖法
位圖,就是用一個或多個 bit 來標記某個元素對應的值,而鍵就是該元素。採用位做爲單位來存儲數據,能夠大大節省存儲空間。

位圖經過使用位數組來表示某些元素是否存在。它能夠用於快速查找,判重,排序等。不是很清楚?我先舉個小例子。

假設咱們要對 [0,7] 中的 5 個元素 (6, 4, 2, 1, 5) 進行排序,能夠採用位圖法。0~7 範圍總共有 8 個數,只須要 8bit,即 1 個字節。首先將每一個位都置 0:

00000000
而後遍歷 5 個元素,首先遇到 6,那麼將下標爲 6 的位的 0 置爲 1;接着遇到 4,把下標爲 4 的位 的 0 置爲 1:

00001010
依次遍歷,結束後,位數組是這樣的:

01101110
每一個爲 1 的位,它的下標都表示了一個數:

for i in range(8):
if bits[i] == 1:
print(i)
這樣咱們其實就已經實現了排序。


對於整數相關的算法的求解,位圖法是一種很是實用的算法。假設 int 整數佔用 4B,即 32bit,那麼咱們能夠表示的整數的個數爲 232。

那麼對於這道題,咱們用 2 個 bit 來表示各個數字的狀態:

  • 00 表示這個數字沒出現過;
  • 01 表示這個數字出現過一次(即爲題目所找的不重複整數);
  • 10 表示這個數字出現了屢次。

那麼這 232 個整數,總共所需內存爲 232*2b=1GB。所以,當可用內存超過 1GB 時,能夠採用位圖法。假設內存知足位圖法需求,進行下面的操做:

遍歷 2.5 億個整數,查看位圖中對應的位,若是是 00,則變爲 01,若是是 01 則變爲 10,若是是 10 則保持不變。遍歷結束後,查看位圖,把對應位是 01 的整數輸出便可。

方法總結
判斷數字是否重複的問題,位圖法是一種很是高效的方法。

題目5

題目描述
給定 40 億個不重複的沒排過序的 unsigned int 型整數,而後再給定一個數,如何快速判斷這個數是否在這 40 億個整數當中?

解答思路
方法一:分治法
依然能夠用分治法解決,方法與前面相似,就再也不次贅述了。

方法二:位圖法
40 億個不重複整數,咱們用 40 億個 bit 來表示,初始位均爲 0,那麼總共須要內存:4,000,000,000b≈512M。

咱們讀取這 40 億個整數,將對應的 bit 設置爲 1。接着讀取要查詢的數,查看相應位是否爲 1,若是爲 1 表示存在,若是爲 0 表示不存在。

方法總結
判斷數字是否存在、判斷數字是否重複的問題,位圖法是一種很是高效的方法。

題目6

題目描述
搜索引擎會經過日誌文件把用戶每次檢索使用的全部查詢串都記錄下來,每一個查詢牀的長度不超過 255 字節。

假設目前有 1000w 個記錄(這些查詢串的重複度比較高,雖然總數是 1000w,但若是除去重複後,則不超過 300w 個)。請統計最熱門的 10 個查詢串,要求使用的內存不能超過 1G。(一個查詢串的重複度越高,說明查詢它的用戶越多,也就越熱門。)

解答思路
每一個查詢串最長爲 255B,1000w 個串須要佔用 約 2.55G 內存,所以,咱們沒法將全部字符串所有讀入到內存中處理。

方法一:分治法
分治法依然是一個很是實用的方法。

劃分爲多個小文件,保證單個小文件中的字符串能被直接加載到內存中處理,而後求出每一個文件中出現次數最多的 10 個字符串;最後經過一個小頂堆統計出全部文件中出現最多的 10 個字符串。

方法可行,但不是最好,下面介紹其餘方法。

方法二:HashMap 法
雖然字符串總數比較多,但去重後不超過 300w,所以,能夠考慮把全部字符串及出現次數保存在一個 HashMap 中,所佔用的空間爲 300w*(255+4)≈777M(其中,4表示整數佔用的4個字節)。因而可知,1G 的內存空間徹底夠用。

思路以下:

首先,遍歷字符串,若不在 map 中,直接存入 map,value 記爲 1;若在 map 中,則把對應的 value 加 1,這一步時間複雜度 O(N)。

接着遍歷 map,構建一個 10 個元素的小頂堆,若遍歷到的字符串的出現次數大於堆頂字符串的出現次數,則進行替換,並將堆調整爲小頂堆。

遍歷結束後,堆中 10 個字符串就是出現次數最多的字符串。這一步時間複雜度 O(Nlog10)。

方法三:前綴樹法
方法二使用了 HashMap 來統計次數,當這些字符串有大量相同前綴時,能夠考慮使用前綴樹來統計字符串出現的次數,樹的結點保存字符串出現次數,0 表示沒有出現。

思路以下:

在遍歷字符串時,在前綴樹中查找,若是找到,則把結點中保存的字符串次數加 1,不然爲這個字符串構建新結點,構建完成後把葉子結點中字符串的出現次數置爲 1。

最後依然使用小頂堆來對字符串的出現次數進行排序。

方法總結
前綴樹常常被用來統計字符串的出現次數。它的另一個大的用途是字符串查找,判斷是否有重複的字符串等。

題目7

題目描述
已知某個文件內包含一些電話號碼,每一個號碼爲 8 位數字,統計不一樣號碼的個數。

解答思路
這道題本質仍是求解數據重複的問題,對於這類問題,通常首先考慮位圖法。

對於本題,8 位電話號碼能夠表示的號碼個數爲 108 個,即 1 億個。咱們每一個號碼用一個 bit 來表示,則總共須要 1 億個 bit,內存佔用約 100M。

思路以下:

申請一個位圖數組,長度爲 1 億,初始化爲 0。而後遍歷全部電話號碼,把號碼對應的位圖中的位置置爲 1。遍歷完成後,若是 bit 爲 1,則表示這個電話號碼在文件中存在,不然不存在。bit 值爲 1 的數量即爲 不一樣電話號碼的個數。

方法總結
求解數據重複問題,記得考慮位圖法。

題目8

題目描述
從 5 億個數中找出中位數。數據排序後,位置在最中間的數就是中位數。當樣本數爲奇數時,中位數爲 第 (N+1)/2 個數;當樣本數爲偶數時,中位數爲 第 N/2 個數與第 1+N/2 個數的均值。

解答思路
若是這道題沒有內存大小限制,則能夠把全部數讀到內存中排序後找出中位數。可是最好的排序算法的時間複雜度都爲 O(NlogN)。這裏使用其餘方法。

方法一:雙堆法
維護兩個堆,一個大頂堆,一個小頂堆。大頂堆中最大的數小於等於小頂堆中最小的數;保證這兩個堆中的元素個數的差不超過 1。

若數據總數爲偶數,當這兩個堆建好以後,中位數就是這兩個堆頂元素的平均值。當數據總數爲奇數時,根據兩個堆的大小,中位數必定在數據多的堆的堆頂。

見 LeetCode No.295:https://leetcode.com/problems/find-median-from-data-stream/
以上這種方法,須要把全部數據都加載到內存中。當數據量很大時,就不能這樣了,所以,這種方法適用於數據量較小的狀況。5 億個數,每一個數字佔用 4B,總共須要 2G 內存。若是可用內存不足 2G,就不能使用這種方法了,下面介紹另外一種方法。

方法二:分治法
分治法的思想是把一個大的問題逐漸轉換爲規模較小的問題來求解。

對於這道題,順序讀取這 5 億個數字,對於讀取到的數字 num,若是它對應的二進制中最高位爲 1,則把這個數字寫到 f1 中,不然寫入 f0 中。經過這一步,能夠把這 5 億個數劃分爲兩部分,並且 f0 中的數都大於 f1 中的數(最高位是符號位)。

劃分以後,能夠很是容易地知道中位數是在 f0 仍是 f1 中。假設 f1 中有 1 億個數,那麼中位數必定在 f0 中,且是在 f0 中,從小到大排列的第 1.5 億個數與它後面的一個數的平均值。

提示,5 億數的中位數是第 2.5 億與右邊相鄰一個數求平均值。若 f1 有一億個數,那麼中位數就是 f0 中從第 1.5 億個數開始的兩個數求得的平均值。
對於 f0 能夠用次高位的二進制繼續將文件一分爲二,如此劃分下去,直到劃分後的文件能夠被加載到內存中,把數據加載到內存中之後直接排序,找出中位數。

注意,當數據總數爲偶數,若是劃分後兩個文件中的數據有相同個數,那麼中位數就是數據較小的文件中的最大值與數據較大的文件中的最小值的平均值。
方法總結
分治法,真香!

題目9

題目描述
有 10 個文件,每一個文件大小爲 1G,每一個文件的每一行存放的都是用戶的 query,每一個文件的 query 均可能重複。要求按照 query 的頻度排序。

解答思路
若是 query 的重複度比較大,能夠考慮一次性把全部 query 讀入內存中處理;若是 query 的重複率不高,那麼可用內存不足以容納全部的 query,這時候就須要採用分治法或其餘的方法來解決。

方法一:HashMap 法
若是 query 重複率高,說明不一樣 query 總數比較小,能夠考慮把全部的 query 都加載到內存中的 HashMap 中。接着就能夠按照 query 出現的次數進行排序。

方法二:分治法
分治法須要根據數據量大小以及可用內存的大小來肯定問題劃分的規模。對於這道題,能夠順序遍歷 10 個文件中的 query,經過 Hash 函數 hash(query) % 10 把這些 query 劃分到 10 個小文件中。以後對每一個小文件使用 HashMap 統計 query 出現次數,根據次數排序並寫入到零外一個單獨文件中。

接着對全部文件按照 query 的次數進行排序,這裏可使用歸併排序(因爲沒法把全部 query 都讀入內存,所以須要使用外排序)。

方法總結

  • 內存若夠,直接讀入進行排序;
  • 內存不夠,先劃分爲小文件,小文件排好序後,整理使用外排序進行歸併。

題目10

題目描述
有 20 個數組,每一個數組有 500 個元素,而且有序排列。如何在這 20*500 個數中找出前 500 的數?

解答思路
對於 TopK 問題,最經常使用的方法是使用堆排序。對本題而言,假設數組降序排列,能夠採用如下方法:

首先創建大頂堆,堆的大小爲數組的個數,即爲 20,把每一個數組最大的值存到堆中。

接着刪除堆頂元素,保存到另外一個大小爲 500 的數組中,而後向大頂堆插入刪除的元素所在數組的下一個元素。

重複上面的步驟,直到刪除完第 500 個元素,也即找出了最大的前 500 個數。

爲了在堆中取出一個數據後,能知道它是從哪一個數組中取出的,從而能夠從這個數組中取下一個值,能夠把數組的指針存放到堆中,對這個指針提供比較大小的方法。

方法總結
求 TopK,不妨考慮一下堆排序?

最後

若是以爲有收穫的話,能夠點個贊和轉發,可讓更多的人看到這篇文章,順便給予我鼓勵喲
若是想學習更多面試技巧,技術乾貨,能夠加一下個人公衆號:碼農富哥(搜索coder2025)


掃碼關注個人公衆號:碼農富哥
相關文章
相關標籤/搜索