目錄
《算法設計手冊》(The Algorithm Design Manual)是本比較經典的算法書了。如果說《算法導論》偏向於數學,那麼《算法設計手冊》更偏向於工程應用(至於《計算機程序設計藝術》,目前我是沒時間通讀,只是偶爾當工具書查查,就不提了)。前者的課後題中的面試題部分挺潮的,如果在google上搜索一下,發現很多都是名企考過的,或許是因爲第二版出版時間比較近的緣故?我不大相信是作者自己出的然後被大公司拿去面試的,而是作者收錄的考過的面試題。有了這一層篩選,這些面試題質量有保證啊。
由於看的是英文版,大部分題都是我翻譯過來的,個人英文水平有限,雖然有的不好理解的地方參照一些解答來幫助理解,並諮詢了在國外留學的朋友,可能仍有些措辭不準確或有誤的地方,懇求諒解並歡迎提出。同時,有的題目用到的比較冷僻的知識可能會和正文有關係,這種情況會明確註明。
雖然官方網站上有個wiki answer提供了大多數答案,不過有的不很合適,我寫的和整理的都是比較好的解答。
原作者的wiki answer頁面http://www2.algorithm.cs.sunysb.edu:8080/mediawiki/index.php已經失效。
第1章:
不用*和/計算整數除法。請找出最快的方式。
解答:
雖然初始化一個計數變量,每當被除數減去除數的一次就自增一直到被除數小於除數這個暴力解法可行,但顯然很慢。這是wiki answer答案,但它在很多情況下都不快,比如100/1。其執行的次數正好和相除的結果相同,用m表示除數,n表示被除數,時間複雜度是O(m/n)。
// Note: This only works for positive values! int divide(int numerator, int denominator) { int quotient = 0; while(numerator >= denominator) { numerator -= denominator; quotient++; } return quotient; }
下面看看另一種解法。
一般限制使用*和/時,很容易考慮使用位移運算來替代,因爲對於無符號數,左移一位(在不溢出時)相當於乘以2,右移一位相當於除以2。如果在紙上進行除法的筆算,是只用到了乘法和減法的。但是一般的十進制整數除法和位運算有什麼關係呢?爲了將兩者建立聯繫,必須把十進制數轉化成二進制數,觀察除法的進行情況來找規律。比如100/7,寫成二進制來進行筆算,計算過程如下圖:
這樣就簡單了,從這個式子可以看出,二進制除法筆算只涉及了減法和隱含的移位與大小比較,原先的乘法已經被移位所代替。因此,具體的編碼,就是把用筆算除法的過程轉化成代碼而已。
不過,一般考慮使用除法的環境,必然要考慮除數是否爲0。除數爲0時這個除法是非法的,不能繼續進行,需要報錯。
既然提到了編碼,如果使用C語言來完成,要注意的是:在C標準中,帶符號數右移的結果在C語言裏是實現相關的,具體結果取決於實現,而不一定是用符號位補、用1補或者用0補最高位。爲了避免這個陷阱,建議先確定結果——也就是商的符號,然後把被除數和除數都轉化爲無符號數,這樣位移時就不會出錯。
但是,這又涉及了有帶符號數與無符號數的轉換,它們二者的表示範圍的問題是不同的。好在被除數和除數從帶符號數轉化爲無符號數時並不會丟失數據,而且商的絕對值必然小於被除數的絕對值(因爲除數是整數,爲0時報錯,大於等於1時才繼續進行),這時把商轉化迴帶符號數時也不會丟失數據,可以放心的進行。不過這一點最好在面試時告訴面試官你已經注意到了這個問題,肯定會爲你的印象加分。
int division(int m,int n) {
//calculate m/n without * and /
unsigned int rest,divisor, op,result = 0;
int flag;
int bits = 0;
//bits用於記錄商的1在哪一位
assert(n!=0);
if((m<0 && n>0) || ( m>0 && n<0 ))
flag = -1;
else
flag = 1;
rest = m>0?m:-m;
divisor = n>0?n:-n;
if(rest < divisor)
return 0;
op = divisor;
/* 2013.8.30 */
/*經過博客園園友infinityu的提醒重寫 */
while(op<=rest) {
bits++;
op=op<<1;
}
op=op>>1;
bits--;
while(op>=divisor) {
if(rest>=op) {
rest-=op;
result += 1<<bits;
}
op = op>>1;
bits--;
}
/* 重寫部分結束 */
return flag * result;
}
由於需要把被除數轉化爲二進制進行計算,最多做了其二進制表示位數次的減法,因此對於被除數m,算法複雜度爲O(logm)。
稍作修改,把最後的小於除數divisor的result取出就是餘數,這樣就能把除法運算改寫爲取模運算%了。如果把參數表修改爲傳遞結果地址,同時獲得商和餘數也是可以的。
可見,這一道面試題考到了算法優化、除法除數爲0這個常見錯誤、將除法從十進制引申到二進制、二進制的位運算、語言特性中的無符號數和帶符號數的位移、無符號數和帶符號數的相互轉換,你還可以更進一步探討算法複雜度、以及算法的擴展性,確實很能考察被面試者對算法的掌握情況。
p.s.經過園友infinityu的提示,發現源代碼中有bug,重寫之後已經對1~1000之間所有整數相互相除的測試。爲了便於記錄商的1應該在哪一位,使用變量bits來指示。
25匹馬,一次最多5匹馬比賽,如何用最少的比賽次數找到最快的前三匹馬?(假設所有馬的速度在每場比賽的發揮都一樣且各匹馬之間不相同)
解答:
老生常談的問題,關鍵是找出每次的正確候選以及儘量利用上次比賽獲得的信息。
先分5組A、B、C、D、E,組內比賽,假設A1爲A組第一。一共5場。
將A1~E1進行比賽,不妨設第一是A1,那麼最快就是A1。
第二快只能在A2、B1~E1中出現。同時,這時知道了B1~E1的速度,不妨B1>C1>D1>E1,這樣D1、E1以及整個D組和E組可以被排除出第二和第三的候選。同時,C2必然不可能是第三快。這時候選爲A2、A3、B1、B2、C1,比賽一次,前兩名即爲第二和第三。
綜上,一共比賽了7次。
=============================================================
1-30~1.34是幾個估算題,當年google確實考過其中的題目。不過這裏不做解答了。
關於估算題目的思路和解法,可以參考《編程珠璣》《編程珠璣(續)》和我寫的相關文章:[珠璣之櫝]估算的應用與Little定律
1.30
世界上有多少個鋼琴調音師?
1.31
美國有多少個加油站?
1.32
曲棍球場上的雪有多重?
1.33
美國公路一共有多長?
1.34
平均來看,你翻開一本曼哈頓電話簿時,你需要隨機翻開多少次才能找到一個給定的名字?