全屏閱讀
[收藏]
10億個正整數,只有其中1個數重複出現過,要在o(n)的時間裡面找出這個數,記憶體要盡可能少(小於100m)。
謝謝absolute同學提出的問題。
部分解答(還有沒有完成的部分):
首先看一下10億個正整數,正整數可以表示的範圍為1到2的31次方-1。
10億也就是1*10^9,2^31次方=2*1024*1024*1024>20億
再想起int為32位。
再想起位圖法。
位圖法也就是對於出現的數,其中每1bit代表這個數,如果該位為1,則說明該數出現;如果該位為0,則說明該數沒有出現。
那多大的記憶體能夠表示10億的數呢?
1 byte = 8 bit
1024 byte = 8*1024 bit = 1k
1024 k = 8*1024*1024 bit = 1m = 8388608 bit
將10,0000,0000處以8388608得到119.20928955078125
也就是差不多120m的記憶體,可以表示全10億的數。
所以可以建立120m的乙個位圖,將其所有位設定為0,然後開始遍歷這10億個整數,每遍歷乙個,則對應到位圖中相應的位置1,如果對應到位圖中相應的位已經置1了,則說明這個數是要找的那個重複的數。用這種方法,最多就是遍歷一遍,將這個10億個正整數遍歷完。而使用的記憶體為120m左右。
當然,題目中要求是小於100m。其實寫到這裡,似乎感覺這個題目是在**看到過。似乎是《程式設計珠璣》或者類似的書中,當然,最初的**肯定是程式設計珠璣,關於**號碼的部分。
於是下一步我就是將這本書翻出來,結果就是在開篇就是關於這個問題。
不過我們遇到的問題是10億個數,100m記憶體。而書中的問題是10^7個正整數,1m的可用主存。書中的問題乘以100,就正好是我們遇到的問題了。不過書中的問題是去掉所有重複的數,並將結果是乙個有序的排列。
如果嚴格的使用100m以下記憶體的話,我們只能利用磁碟作為虛擬儲存空間。
如果使用磁碟的話,應該就會涉及到外排序之類的。
或者是虛擬記憶體的管理,頁面的換入換出?
其實我們這裡的問題並不需要完全排序,而只是需要找出重複的數就可以。是否可以不用排序就得到?
關於兩通道演算法,我得複習一下相關書籍了。。。
再想想,其實題目出的有問題,應該是最大不會超過10億,不然位圖法也不行。或者就需要做hash來得到對應關係了。
增加內容:
在寫了這篇部落格之後,又將《程式設計珠璣》拿出來將裡面的內容看了一些,不過倒是有些地方沒看明白了,這裡寫出來看看有沒有同學能看出來我**理解錯了(應該不會是寫錯了)
一開始程式設計師提出的問題很簡單,「我該如何對磁碟檔案進行排序」。
【外排序,解決方案一 – 歸併排序】
ok,首先就找本「常見的資料結構書」來看看外排序吧。我這裡原本有兩本,不過有本書不知道扔在**,就取了殷人昆的那本黃書~其9.7節介紹了外排序。
其中介紹了,外排序多使用歸併排序的方法。
書中將排序過程分為兩個階段,第乙個階段建立為外排序所用的記憶體緩衝區,根據它們的大小將輸入檔案劃分為若干段,然後用某種有效的內排序方法對各段進行排序,這些經過排序的段叫做初始歸併段或初始順串,當它們生成後就被寫到外存中去;
第二個階段仿照內排序中所介紹過的歸併樹模式,把第乙個階段生成的初始歸併段加以歸併,一趟趟地擴大歸併段和減少歸併段個數,直到最後歸併成乙個大歸併段(有序檔案)為止。
這裡第乙個階段我們應該比較熟悉,其實和內排序差不多,區別就是分段輸入,以及輸出初始歸併段到硬碟檔案中。
外排序和內排序主要的區別就是在將硬碟中的多個有序檔案歸併到乙個最終的有序檔案。
過程:簡單的2路歸併,對兩個歸併段進行歸併時。僅需把這兩個歸併段中的物件逐塊讀入記憶體,進行比較後,寫入大歸併段,然後再進行讀出,所以這種方法能夠對很大的歸併段進行歸併。而其他的內排序方法很難用於外排序。包括插入排序、希爾排序、氣泡排序、快速排序、選擇排序、堆排序、基數排序。
這種方法,需要1次讀入輸入檔案,多次讀入/讀出中間檔案,1次讀出輸出檔案。
之後回到書上的內容來,此時作者回答了程式設計師的問題,在流行程式設計書籍中的磁碟排序程式大概有10多個函式,200行程式**(有這麼多嗎?)對於這些**,實現和測試大概最多需要花費程式設計師1周的時間。
這當然不是最好的答案,所以才有了之後的交談。還是用談話的內容來介紹這一段比較好。斜體為作者的問題,然後是程式設計師的回答。
需要排序的內容究竟是什麼?檔案中有多少記錄?每個記錄的格式是什麼?
該檔案包含至多10000000個記錄,每條記錄都是乙個7位整數。
等一下。假如檔案那麼小的話,為什麼還要費力地使用磁碟排序呢?為什麼不在主儲存器中對它進行排序呢?
儘管機器有很多mb的主儲存器,但是該排序功能屬於某個大型系統中的一部分。我想實際上我可能只有1mb的空閒主存。(乙個可以看出以前就算是大型系統,記憶體還是很緊俏啊,還有就是如何能夠估算出自己能夠使用的記憶體呢?因為現在我們的程式都是架在虛存上面的,每個程序都有屬於自己的4g空間。)
你能將有關記錄方面的內容說得更詳細一點嗎?
每個記錄都是乙個7位正整數,並且沒有其他的關聯資料,每個整數至多只能出現一次。
作者主要通過上面的對話,將問題了解得更清楚了。同時,根據其他的對話,最終了解到這個文字是美國的**號碼的乙個儲存檔案。在美國,**號碼由3位區號與7位其他號碼組成。撥打包含免費區號800的**是不收費的。實際的免費**號碼資料庫包含有大量的資訊,包括免費**號碼,撥打的實際號碼,使用者名稱和位址等等。
程式設計師所要處理的就是這樣的乙個文字資料庫,將要進行排序的整數就是那些免費**號碼。輸入檔案是乙個號碼列表(其他資訊都被刪除了。由此可見,即使是這樣的乙個系統內的乙個小模組,也在前面還做了其他的工作,這裡的輸入檔案已經是經過處理的了。),並且同一號碼出現兩次以上將是乙個錯誤(那這個錯誤是否需要處理,由誰來保證?)。預期的輸出是乙個包含大量號碼,並且以公升序的方式進行排序的檔案。
同時關於效能,實際環境同時也定義了效能需求。在與系統進行長時間的會話期間,使用者請求排序檔案的頻率大約是每小時一次。在完成排序之前,使用者不能做任何事情。因此排序時間不能太長,最合適的執行時間是10秒鐘。
【整理之後,精確的問題陳述】
輸入:所輸入的是乙個檔案,至多包含n個正整數,每個正整數都要小於n,這裡的n為10^7。如果輸入時某乙個整數出現了兩次,就會產生乙個致命的錯誤。這些整數與其他任何資料都不關聯。
輸出:以增序形式輸出經過排序的整數列表。(這裡應該補充一下是檔案形式嗎?)
約束:至多(大概)只有1mb的可用主存,但是可用磁碟空間非常充足。執行時間至多只允許幾分鐘,最適宜的時間大概為10秒鐘。
【解決方案二 – 多通道排序】
將每個號碼儲存在7個位元組(byte)裡,1m空間共有=1024*1024=1048576 bytes。
所以1m中共能儲存149796個號碼。
如果將每個號碼表示成32位的整數(也就是4個bytes),那1m中共能儲存262144個號碼。
(書中的數字分別為143000和250000。)
下面就對這種情況下,使用多通道程式進行外排序。
因為一共有10000000個整數,而1m中我們使用250000個號碼儲存,所以需要使用40個通道。
第乙個通道中將0到249999之間的任意整數讀到記憶體中,並對這250000個整數進行排序,然後將它們寫入輸出檔案中。
第二個通道對250000到499999之間的整數進行排序,然後也是同樣寫入輸出檔案中。
依次類推,直到第40個通道。
第40個通道將排序9750000到9999999之間的整數,然後寫入輸出檔案中。
注意,這裡意思是輸出檔案是同乙個檔案。
快速排序在主存中相當有效,它只需要20行**。因此整個程式只需1到2頁的**即可實現,並且該程式還有乙個令人滿意的特性,即我們不必擔心使用中間磁碟檔案的問題。
40個通道的演算法不使用中間檔案,需要多次讀取輸入檔案,但只進行一次輸出檔案的寫入操作。
有些疑問,怎麼做到是同乙個輸出檔案的。難道輸出之後,整個檔案就是有序的了?沒有想明白。大家看看呢?
面試題之10億正整數問題
10億個正整數,只有其中1個數重複出現過,要在o n 的時間裡面找出這個數,記憶體要盡可能少 小於100m 謝謝absolute同學提出的問題。部分解答 還有沒有完成的部分 首先看一下10億個正整數,正整數可以表示的範圍為1到2的31次方 1。10億也就是1 10 9,2 31次方 2 1024 1...
亞馬遜面試題目 最小的回文正整數
題目要求 給定正整數 n,求其 下乙個最小的回文正整數。比如,n 9,則下乙個所求之數為11。又如n 12444,則下乙個所求之數為12521.下面給出了兩種實現方法 getfirstbiggerpalindromev2是本博主寫的,而getfirstbiggerpalindrome 是寫的。inc...
面試題 整數取反
完成函式reverse,要求實現把給定的乙個整數取其相反數的功能,舉兩個例子如下 x 123,return 321 x 123,return 321 位址是我提交的 是 reverseintergertest.cpp 定義控制台應用程式的入口點。include stdafx.h include in...