《算法設計手冊》面試題解答 第一章:算法設計簡介


目錄

 

系列簡介:

  《算法設計手冊》(The Algorithm Design Manual)是本比較經典的算法書了。如果說《算法導論》偏向於數學,那么《算法設計手冊》更偏向於工程應用(至於《計算機程序設計藝術》,目前我是沒時間通讀,只是偶爾當工具書查查,就不提了)。前者的課后題中的面試題部分挺潮的,如果在google上搜索一下,發現很多都是名企考過的,或許是因為第二版出版時間比較近的緣故?我不大相信是作者自己出的然后被大公司拿去面試的,而是作者收錄的考過的面試題。有了這一層篩選,這些面試題質量有保證啊。

  由於看的是英文版,大部分題都是我翻譯過來的,個人英文水平有限,有的不好理解的地方盡量參照相關解答來理解,並咨詢了在國外留學的朋友,可能仍有些措辭不准確或有誤的地方,懇求諒解並歡迎提出。同時,有的題目用到的比較冷僻的知識可能會和正文有關系,這種情況會明確注明。

  雖然官方網站上有個wiki answer提供了大多數答案,不過有的不很合適,我寫的和整理的都是比較好的解答。

  原作者的wiki answer頁面http://www2.algorithm.cs.sunysb.edu:8080/mediawiki/index.php已經失效。

  另附上在線勘誤表:http://www.cs.sunysb.edu/~skiena/algorist/book/errata

 

第1章:

1-28

  不用*和/計算整數除法。請找出最快的方式。

解答:

  雖然初始化一個計數變量,每當被除數減去除數的一次就自增一直到被除數小於除數這個暴力解法可行,但顯然很慢。這是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來指示。

 

1-29:

  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,比賽一次,前兩名即為第二和第三。

  (注意:這里分析時沒有"充分"利用所有已知信息。更進一步利用已知信息的方式請看擴展1。

  綜上,一共比賽了7次。

 

擴展1:

  64匹馬,每次最多8匹比賽,要求用最少場次獲得前4名。其他條件同上題。

 解答:

  按照上題的分析方式並不能得到最少比賽次數,下面看看如何充分利用已知信息來達到最少比賽次數。

  首先分8組A~H決出各組順序,共需8場,並且組內順序排列為A1>A2>...>A8。

  第一名在A1~H1中決出,不妨設為A1>B1>...>H1,需要1場比賽。

  此時第二名只能是A2和B1其中之一(不同於上題分析,C1~H1其實可以直接拋棄),但決出第二名只用兩個賽道太浪費了。為此進一步分析,如果A2>B1,那么第3名只能是A3、B1之一;如果B1>A2,那么第3名只能是A2、B2、C1。這兩種情況都只涉及5匹馬仍然不滿8匹。用這種思路進行全面分析,表示為樹狀並把葉子處需要比較的馬的編號進行標注:

  角逐第2名時A2>B1

    第3名候選A3,B1

    角逐第3名時A3>B1

      第4名候選A4,B1     ————(A2,A3,A4,B1)

    角逐第3名時A3<B1

      第4名候選A3,B2,C1    ————(A2,A3,B1,C1)

  角逐第2名時A2<B1

    第3名候選A2,B2,C1

    角逐第3名時A2>B2>C1

      第4名候選B2,A3     ————(A2,A3,B1,B2,C1)

    角逐第3名時A2>C1>B2

      第4名候選C1,A3     ————(A2,A3,B1,B2,C1)

    角逐第3名時B2>A2>C1

      第4名候選A2,B3     ————(A2,B1,B2,B3,C1)

    角逐第3名時B2>C1>A2

      第4名候選B3,C1     ————(A2,B1,B2,B3,C1)

    角逐第3名時C1>A2>B2

      第4名候選A2,C2,D1    ————(A2,B1,B2,C1,C2,D1)

    角逐第3名時C1>B2>A2

      第4名候選C2,B2,D1    ————(A2,B1,B2,C1,C2,D1)

  可見,如果能夠一次比賽獲得A2,A3,A4,B1,B2,B3,C1,C2,D1的完整排列次序才能知道前2~4名。很可惜一共是9匹馬,不可能一場比賽必然獲得結果。那么求最優方案,就是將以上各分支出現最晚出現的馬去掉,即C2或D1,進行一場比賽。運氣好的話這一輪可以決出前2~4,一共比賽10輪,運氣不好的話還需要加賽一輪。而去掉C2或D1能夠保證只比賽10輪的概率最大。

 

擴展2:

  25匹馬,5個賽道,決出前5。

解答:

  分析和“擴展1”類似,留給讀者自己完成。如果想校對答案,可以查閱:http://blog.csdn.net/hackbuteer1/article/details/7481342

 

=============================================================

  1-30~1.34是幾個估算題,當年google確實考過其中的題目。不過這里不做解答了。

  關於估算題目的思路和解法,可以參考《編程珠璣》《編程珠璣(續)》和我寫的相關文章:[珠璣之櫝]估算的應用與Little定律

  另外,“不用再去算一輛校車上可以裝多少個高爾夫球了。因為 Google 已承認,那些用於測試求職者的智力題/腦筋急轉彎(全世界有多少鋼琴調音師?為什么井蓋是圓的?),就無法預測出求職者是否會成為一位好員工。 “現在信賴更常規方法去面試潛在員工”。相關鏈接

1.30

  世界上有多少個鋼琴調音師?

1.31

  美國有多少個加油站?

1.32

  曲棍球場上的雪有多重?

1.33

  美國公路一共有多長?

1.34

  平均來看,你翻開一本曼哈頓電話簿時,你需要隨機翻開多少次才能找到一個給定的名字?


免責聲明!

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



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