騰訊筆試題


1、解釋const的含義及實現機制
const的含義及實現機制,比如:const int i,是怎么做到i只可讀的?
答:const用來說明所定義的變量是只讀的。
這些在編譯期間完成,編譯器可能使用常數直接替換掉對此變量的引用。

2、買200返100優惠券,實際上折扣是多少?

到商店里買200的商品返還100優惠券(可以在本商店代替現金)。請問實際上折扣是多少?
答:由於優惠券可以代替現金,所以可以使用200元優惠券買東西,然后還可以獲得100元的優惠券。
假設開始時花了x元,那么可以買到 x + x/2 + x/4 + ...的東西。所以實際上折扣是50%.(當然,大部分時候很難一直兌換下去,所以50%是折扣的上限)
如果使用優惠券買東西不能獲得新的優惠券,那么
總過花去了200元,可以買到200+100元的商品,所以實際折扣為 200/300 = 67.7%.

3、簡述tcp三次握手的過程,accept發生在三次握手哪個階段?

要想明白Socket連接,先要明白TCP連接。設備能夠使用聯網功能是因為設備底層實現了TCP/IP協議,可以使設備終端通過無線網絡建立TCP連接。TCP協議可以對上層網絡提供接口,使上層網絡數據的傳輸建立在“無差別”的網絡之上。
建立起一個TCP連接需要經過“三次握手”:
第一次握手:客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。

三次握手完成后,客戶端和服務器就建立了tcp連接。這時可以調用accept函數獲得此連接。


握手過程中傳送的包里不包含數據,三次握手完畢后,客戶端與服務器才正式開始傳送數據。理想狀態下,TCP連接一旦建立,在通信雙方中的任何一方主動關閉連接之前,TCP 連接都將被一直保持下去,如下圖:

斷開連接時服務器和客戶端均可以主動發起斷開TCP連接的請求,斷開過程需要經過“四次揮手”(過程就不細寫了,就是服務器和客戶端交互,最終確定斷開,如下圖)
由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這個原則是當一方完成它的數據發送任務后就能發送一個FIN來終止這個方向 的連接。收到一個 FIN只意味着這一方向上沒有數據流動,一個TCP連接在收到一個FIN后仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
(1)主動端A發送一個FIN,用來關閉客戶A到被動端B的數據傳送。
(2)被動端B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1。和SYN一樣,一個FIN將占用一個序號。
(3)被動端B關閉與主動端A的連接,發送一個FIN給主動端A。
(4)主動端A發回ACK報文確認,並將確認序號設置為收到序號加1。

4、用UDP協議通訊時怎樣得知目標機是否獲得了數據包?

答:可以在每個數據包中插入一個唯一的ID,比如timestamp或者遞增的int。發送方在發送數據時將此ID和發送時間記錄在本地。接收方在收到數據后將ID再發給發送方作為回應發送方如果收到回應,則知道接收方已經收到相應的數據包;如果在指定時間內沒有收到回應,則數據包可能丟失,需要重復上面的過程重新發送一次,直到確定對方收到。

5、求一個論壇的在線人數,假設有一個論壇,其注冊ID有兩億個,每個ID從登陸到退出會向一個日志文件中記下登陸時間和退出時間,要求寫一個算法統計一天中論壇的用戶在線分布,取樣粒度為秒。
答:一天總共有 3600*24 = 86400秒。
定義一個長度為86400的整數數組int delta[86400],每個整數對應這一秒的人數變化值,可能為正也可能為負。開始時將數組元素都初始化為0。
然后依次讀入每個用戶的登錄時間和退出時間,將與登錄時間對應的整數值加1,將與退出時間對應的整數值減1。
這樣處理一遍后數組中存儲了每秒中的人數變化情況。
定義另外一個長度為86400的整數數組int online_num[86400],每個整數對應這一秒的論壇在線人數。
假設一天開始時論壇在線人數為0,則第1秒的人數online_num[0] = delta[0]。第n+1秒的人數online_num[n] = online_num[n-1] + delta[n]。
這樣我們就獲得了一天中任意時間的在線人數。

6、在一個文件中有 10G 個整數,亂序排列,要求找出中位數。內存限制為 2G。
答:不妨假設10G個整數是64bit的。
2G內存可以存放256M個64bit整數。
我們可以將64bit的整數空間平均分成256M個取值范圍,用2G的內存對每個取值范圍內出現整數個數進行統計。這樣遍歷一邊10G整數后,我們便知道中數在那個范圍內出現,以及這個范圍內總共出現了多少個整數。
如果中數所在范圍出現的整數比較少,我們就可以對這個范圍內的整數進行排序,找到中數。如果這個范圍內出現的整數比較多,我們還可以采用同樣的方法將此范圍再次分成多個更小的范圍(256M=2^28,所以最多需要3次就可以將此范圍縮小到1,也就找到了中數)。

拿到此題目首先考慮的是內存的限制,因而無法用快速排序或是部分排序。若是求的是最大值或最小值,或是K小的值(k<2G)則可以采用堆排序O(NlogK)。但現在是求中位數即排在第5G和5G+1的數

算法思路分析:假設是無符號整數,

第一步: 借鑒桶排序的思路,因為整數為32位,我們先按高16位2^16=64K進行計數,即分成64K段,這樣需要的計數數組大小為2^16,若數組類型為int型,存在缺點,若10G的數都是相同,這樣數組存的計數最大為2^32=4G,就會出現溢出,所以數組類型采用long long8字節型。占用內存為2^16*8B=518KB。而內存給了2G,可見利用得過少,表明還有很大的改進空間。 改進:分成2G/8B=2^28=256M段,這樣段越多,第二步掃描分析的數據就越少。

long long Counter[1 < <28];//256M桶

unsigned int x; 
memset(Counter,0,sizeof(Counter));

foreachnumber(x)
{ 
Counter[x>>4]++; //高28位
}

long long sum=0; 
for(i=0;i <1 < <28;i++)
{ 
sum+=Counter[i]; 
if(sum>=5LL < <30)break;//找到中位數所在的段
} 
sum-=5LL < <30; 
sum=Counter[i]-sum;//為達到5G,中位數所在的段需要的個數

第二步:前步已把10G數據按高28位分到了256M桶中,且已經找到中位數在哪一段,只要把此段按低4位分到16個段中,即可以找到

int segment=i; 
memset(Counter,0,sizeof(Counter)); 
foreachnumber(x)
{ 
if((x>>16)==segment)
{ 
   Counter[x&(~((-1) < <16))]++; //低4位。 -1的8位二進制表示為11111111
} 
} 
long long lsum=0; 
for(i=0;i <1 < <4;i++)
{ 
lsum+=Counter[i]; 
if(lsum>=sum)break;

}
int keynum = (segment<<4)|(i);

總共只要讀兩遍整數,對每個整數也只是常數時間的操作,總體來說是線性時間

若是有符號的整數,只需改變映射即可

解法三:

關於中位數:數據排序后,位置在最中間的數值。即將數據分成兩部分,一部分大於該數值,一部分小於該數值。中位數的位置:當樣本數為奇數時,中位數=(N+1)/2 ; 當樣本數為偶數時,中位數為N/2與1+N/2的均值(那么10G個數的中位數,就第5G大的數與第5G+1大的數的均值了)。

分析:明顯是一道工程性很強的題目,和一般的查找中位數的題目有幾點不同。
1. 原數據不能讀進內存,不然可以用快速選擇,如果數的范圍合適的話還可以考慮桶排序或者計數排序,但這里假設是32位整數,仍有4G種取值,需要一個16G大小的數組來計數。

2. 若看成從N個數中找出第K大的數,如果K個數可以讀進內存,可以利用最小或最大堆,但這里K=N/2,有5G個數,仍然不能讀進內存。

3. 接上,對於N個數和K個數都不能一次讀進內存的情況,《編程之美》里給出一個方案:設k<K,且k個數可以完全讀進內存,那么先構建k個數的堆,先找出第0到k大的數,再掃描一遍數組找出第k+1到2k的數,再掃描直到找出第K個數。雖然每次時間大約是nlog(k),但需要掃描ceil(K/k)次,這里要掃描5次。

解法:首先假設是32位無符號整數。
1. 讀一遍10G個整數,把整數映射到256M個區段中,用一個64位無符號整數給每個相應區段記數。
說明:整數范圍是0 - 2^32 - 1,一共有4G種取值,映射到256M個區段,則每個區段有16(4G/256M = 16)種值,每16個值算一段, 0~15是第1段,16~31是第2段,……2^32-16 ~2^32-1是第256M段。一個64位無符號整數最大值是0~8G-1,這里先不考慮溢出的情況。總共占用內存256M×8B=2GB。

2. 從前到后對每一段的計數累加,當累加的和超過5G時停止,找出這個區段(即累加停止時達到的區段,也是中位數所在的區段)的數值范圍,設為[a,a+15],同時記錄累加到前一個區段的總數,設為m。然后,釋放除這個區段占用的內存。

3. 再讀一遍10G個整數,把在[a,a+15]內的每個值計數,即有16個計數。

4. 對新的計數依次累加,每次的和設為n,當m+n的值超過5G時停止,此時的這個計數所對應的數就是中位數。

總結:
1.以上方法只要讀兩遍整數,對每個整數也只是常數時間的操作,總體來說是線性時間。

2. 考慮其他情況。
若是有符號的整數,只需改變映射即可。若是64為整數,則增加每個區段的范圍,那么在第二次讀數時,要考慮更多的計數。若過某個計數溢出,那么可認定所在的區段或代表整數為所求,這里只需做好相應的處理。噢,忘了還要找第5G+1大的數了,相信有了以上的成果,找到這個數也不難了吧。

3. 時空權衡。
花費256個區段也許只是恰好配合2GB的內存(其實也不是,呵呵)。可以增大區段范圍,減少區段數目,節省一些內存,雖然增加第二部分的對單個數值的計數,但第一部分對每個區段的計數加快了(總體改變??待測)。

4. 映射時盡量用位操作,由於每個區段的起點都是2的整數冪,映射起來也很方便。

 7、兩個整數集合A和B,求其交集。

答:(1) 讀取整數集合A中的整數,將讀到的整數插入到map中,並將對應的值設為1。
(2)讀取整數集合B中的整數,如果該整數在map中並且值為1,則將此數加入到交集當中,並將在map中的對應值改為2。
通過更改map中的值,避免了將同樣的值輸出兩次。

8、有1到10w這10w個數,去除2個並打亂次序,如何找出那兩個數?
答:解法一:申請10w個bit的空間,每個bit代表一個數字是否出現過。
開始時將這10w個bit都初始化為0,表示所有數字都沒有出現過。
然后依次讀入已經打亂循序的數字,並將對應的bit設為1。
當處理完所有數字后,根據為0的bit得出沒有出現的數字。
解法二:首先計算1到10w的和,平方和。
然后計算給定數字的和,平方和。
兩次的到的數字相減,可以得到這兩個數字的和,平方和。
所以我們有
x + y = n
x^2 + y^2 = m
解方程可以得到x和y的值。

9、有1000瓶水,其中有一瓶有毒,小白鼠只要嘗一點帶毒的水24小時后就會死亡,至少要多少只小白鼠才能在24小時時鑒別出那瓶水有毒?
答:最容易想到的就是用1000只小白鼠,每只喝一瓶。但顯然這不是最好答案。
既然每只小白鼠喝一瓶不是最好答案,那就應該每只小白鼠喝多瓶。那每只應該喝多少瓶呢?
首先讓我們換種問法,如果有x只小白鼠,那么24小時內可以從多少瓶水中找出那瓶有毒的?
由於每只小白鼠都只有死或者活這兩種結果,所以x只小白鼠最大可以表示2^x種結果。如果讓每種結果都對應到某瓶水有毒,那么也就可以從2^x瓶水中找到有毒的那瓶水。那如何來實現這種對應關系呢?
第一只小白鼠喝第1到2^(x-1)瓶,第二只小白鼠喝第1到第2^(x-2)和第2^(x-1)+1到第2^(x-1) + 2^(x-2)瓶....以此類推。
回到此題,總過1000瓶水,所以需要最少10只小白鼠。

10、給40億個不重復的unsigned int的整數,沒排過序的,然后再給幾個數,如何快速判斷這幾個數是否在那40億個數當中?

答:unsigned int 的取值范圍是0到2^32-1。我們可以申請連續的2^32/8=512M的內存,用每一個bit對應一個unsigned int數字。首先將512M內存都初始化為0,然后每處理一個數字就將其對應的bit設置為1。當需要查詢時,直接找到對應bit,看其值是0還是1即可。

 


免責聲明!

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



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