1. 程式人生 > >九章演算法——面試題思路

九章演算法——面試題思路

面試題1 落單的數

題目描述:

有2n+1個數,其中2n個數兩兩成對,1個數落單,找出這個數。要求O(n)的時間複雜度,O(1)的空間複雜度。進階問題:如果有2n+2個數,其中有2個數落單,該怎麼辦?

答:

初階:將2n+1個數異或起來,相同的數會抵消,異或的答案就是要找的數。

進階:假設兩個不同的數是a和b,並且a!=b,將2n+2個數異或起來就會得到c=a xor b,並且c不等於0。因此在c的二進位制位中找到一個為1的位,可推斷在這位上a和b分別為0和1,因此將2n+2個數分為該位位0的組和該位為1的組,兩組中各自會包含2n’+1個數和2n’’+1個數,用初階的演算法即可解決。

面試官說:

該問題的考點,在於異或符號的運用。異或運算是計算機系的基礎知識。上過課的同學一般來會答得上第一問。第二問會不會被問到看面試官心情。一般來說,對於一個問題的擴充套件問題,解題的思路是如何將它通過一定的變換轉換為初級問題。因此就要去想怎麼分為兩組,兩組各包含2x+1個數就問題得解。會初階問題以後,基本上會想到把所有數異或起來,得到a xor b。再根據題目中的題目a與b不同,意味著該值不為0,從而想到根據該值的二進位制位為1的位來將2n+2個數分為兩組。

面試題2 抄書問題

有n本書和k個抄寫員。要求n本書必須連續的分配給這k個抄寫員抄寫。也就是說前a1本書分給第一個抄寫員,接下來a2本書分給第二個抄寫員,如此類推(a1,a2需要你的演算法來決定)。給定n,k和每本書的頁數p1,p2..pn,假定每個抄寫員速度一樣(每分鐘1頁),k個抄寫員同時開始抄寫,問最少需要多少時間能夠將所有書全部抄寫完工?(提示:本題有很多種演算法可以在不同的時間複雜度下解決,需要儘可能的想到所有的方法)

答:

解法1:動態規劃 設f[i][j]代表前i本書分給j個抄寫員抄完的最少耗時。答案就是f[n][k]。狀態轉移方程f[i][j] = min{max(f[x][j-1], sum(x+1, i)), j<x<i}。其中x是在列舉第j個抄寫員是從哪本書開始抄寫。 時間複雜度O(n^2*k)

解法2:動態規劃+決策單調。 同上一解法,但在x的列舉上進行優化,設s[i][j]為使得f[i][j]獲得最優值的x是多少。根據四邊形不等式原理,有s[i][j-1]>=s[i][j]>=s[i-1][j]。因此x這一層的列舉不再是每次都是n而是總共加起來n。 時間複雜度O(n*k)

解法3:二分答案。二分最慢的時間,然後嘗試一本本的加進來,加滿了就給一個抄寫員。看最後需要的抄寫員數目是多餘k個還是少於k個,然後來決定是將答案往上調整還是往下調整。 時間複雜度O( n log SUM(pi) )

面試官說

該問題的考點在於對演算法能力進行評級。需要一定的演算法知識積累,如對動態規劃和二分法需要有一定了解。面試官不一定會需要你答出所有的解法,但一般來講至少需要答出O(n^2*k)的動態規劃的解法。

面試題3 找壞球

有12個球,1個沒有砝碼的天秤。其中有11個球的重量是一樣的,另外1個是壞球,和其他球的重量不一樣,但無法確定是輕了還是重了。請問如何用天秤稱3次,就找到壞球並確定是輕了還是重了。(沒有砝碼的天秤只能比較出兩邊誰重誰輕或是重量相等,無法求得具體的重量差)

答:

將球進行編號: 1 2 3 4 5 6 7 8 9 10 11 12,分為三組:(1,2,3,4) (5,6,7,8) (9,10,11,12)

第一稱:稱前兩組。

   相等:可以知道8個球都是好的。

       第二次:稱(1,2,3)和(9,10,11)。

            相等:12是壞球,用1和12稱第三次就知道是重還是輕。

            不等:9,10,11 有壞球,並且已經知道是輕還是重。第三次稱9和10就可以得到結果。

   不等:假設(1,2,3,4) < (5,6,7,8) (反過來的情況同理),並且知道了9,10,11,12是好球。

        第二次:稱(1,2,5)和(3,4,6)。

            相等:7和8有一個重,稱第三次即可。

            不等:假設(1,2,5)<(3,4,6)(反過來類似)。說明1,2輕了,或者6重了,第三次稱1,2即可。

面試官說:

一般問這個問題的公司就是想找平時喜歡研究智力題的人,或者他們公司的招聘名額很少。這是一個十分經典的智力問題。一般來說很難要求面試者在幾分鐘之內考慮周全。所以碰到這樣的問題,知道就是知道,不知道就是不知道。不過即便不知道,比較聰明的面試者還是能夠大致說出一些思路。比如一開始分成3堆,這個思路答上就會加分。如果分成2堆6個和6個,你會發現基本是解不出來的。所以這個題目純粹是考驗你是否是一個聰明的面試者或者你是不是準備面試題的時候做過了(那也說明你態度很端正)。

面試題4 索引比例

估算Baidu和Google的網頁索引數量之比。

答:

我們可以假設能夠通過搜尋引擎做到如下的兩件事:

1. 隨機取到一個網頁

2. 判斷某個網頁(url)是否被索引

因此,在Baidu上多次隨機關鍵詞進行搜尋,獲取到每個關鍵詞對應結果的若干網頁資訊(url),將這些url在Google上查詢是否被索引到。從而得到Baidu網頁中Google索引的的比例為1/B。

對Google做同樣的事情,得到Google網頁中被Baidu索引的比例1/G。由此可知Baidu和Google的索引比例為B:G

面試題5 有序數組合並

初階:合併兩個有序陣列A和B,使得結果依然有序。

進階:合併兩個有序陣列A和B,假設A有n個數,B有m個數,A陣列後面還有m個空餘空間,需要將結果儲存在A中。

答:

一種解答當然是把兩個陣列放在一起重新排序了。這樣的時間複雜度是O(nlogn),沒有用到陣列已經有序的條件,所以顯然不是一個期望的解答。那麼既然A和B已經有序,假設從小到大排序了,那麼A和B中最小的數一定是A[0]和B[0]中最小的,由此每次比較A和B頭上的數,然後拿出最小的,執行O(n)次運算後,即可得到A和B合併之後的有序陣列。

對於進階問題,實際上只需要轉個彎就可以了。因為做過了第一個問題以後很容易會想到比較最小的兩個數,然後就發現需要插入操作了。

面試題6 負載均衡

設計一個用於負載均衡的資料結構,支援加入一臺機器,撤出一臺機器,在活躍的機器集合中“等概率”隨機選中一臺機器。以上三個操作要儘可能的快。

答:

用一個數組記錄當前我的活躍機器集,用一個hash記錄某個機器在陣列中的位置。對於等概率隨機選中一臺機器,random(陣列長度)選中一臺機器即可;對於加入一臺機器,在陣列最後新增即可;對於撤出一臺機器,先用hash找到其在陣列中的對應位置,用陣列最後一個位置的機器和它交換,並在hash表中刪除撤出的機器並修改被交換的機器的位置,這樣做的目的是保證陣列中不會出現空位,這樣才能保證隨機操作的正確性和高效。三個操作的時間複雜度均為O(1)。

面試官說:

本題中描述的負載均衡是用於Web Server的負載均衡,並不是儲存的負載均衡,所以無需考慮新增加的機器需要儘量多的承載請求。本題是純粹的資料結構題,並非設計題。當看到加入一臺機器和撤出一臺機器的時候,自然會想到使用hash表來支援O(1)的插入和O(1)的刪除。但普通的hash表是不支援等概率隨機訪問的。想要支援等概率隨機訪問,那最簡單的方法當然是地址空間連續的陣列。因此想到結合兩種資料結構。剩下來需要解決的問題就是如果讓陣列支援O(1)的刪除並讓陣列沒有空位。一個思維誤區是整體移動後面的資料。實際上由於陣列所代表的內容是集合,無需保證其結果的連續性,因此採用類似堆的刪除操作的方法——用最後一個元素覆蓋待刪除元素,即刻解決問題。

面試題7 分層遍歷二叉樹

初階:給一棵二叉樹,按照層次進行輸出,第一行輸出第一層的節點,第二行輸出第二層,如此類推。

進階:如果只給你O(h)的額外空間該怎麼辦?(h為樹的高度)

答:

 初階:採用寬度(廣度)優先搜尋演算法BFS。用一個佇列儲存一層的節點,通過一層節點擴展出下一層節點。實現的時候有兩種方式:一種方式是佇列中同時儲存層數,發現層數不同了,就換行輸出;另一種方式是記錄每一層的頭尾,多套一層迴圈輸出每一層。時間複雜度O(n),空間複雜度O(n)

進階:採用迭代搜尋。迭代搜尋的意思是,設定一個層數限制x,利用深度優先搜尋的方式往下搜尋,每次搜到x這一層就不再往下繼續遞迴了。通過逐漸放寬x來實現每一層的搜尋,也就是x從1到h進行列舉(h為樹的高度)。時間複雜度O(nh),空間複雜度O(h)。迭代搜尋是常用的在空間不足的情況下替代寬度優先搜尋的方法。是一種用時間換取空間的方法。

面試官角度:

考察對於搜尋的基礎知識熟練程度。深度優先搜尋,寬度優先搜尋,迭代搜尋,是最常見的三種搜尋方式。其中初階問題,還會考察對寬度優先搜尋實現的掌握,這是諸多IT公司面試都會考察的內容。

面試題8 第k大的數

初階:有兩個陣列A和B,假設A和B已經有序(從大到小),求A和B陣列中所有數的第K大。

進階:有N臺機器,每臺機器上有一個有序大陣列,需要求得所有機器上所有數中的第K大。注意,需要考慮N臺機器的平行計算能力。

答:

初階:比較A[k/2]和B[k/2],如果A[k/2]>=B[k/2]那麼A的前k/2個數一定都在前k-1大中,將A陣列前k/2個數扔掉,反之扔掉B的前k/2個數。將k減小k/2。重複上述操作直到k=1。比較A和B的第一個數即可得到結果。時間複雜度O(logk)

進階:二分答案S,將S廣播給每臺機器,每臺機器用二分法求得有多少比該數小的數。彙總結果後可判斷是該將S往上還是往下調整。

面試官角度:

初階問題是一個比較難度大的演算法題。需要有一定的演算法訓練功底。主要用到的思想是遞迴。首先容易想到的方法是合併兩個陣列(見面試題5,有序陣列的合併),這樣複雜度為O(k),那麼答出這個以後,面試官會問你,還有更好的方法麼?這個時候就要往O(logk)的思路去想,O(logk)就意味著需要用一種方法每次將k的規模減小一半,於是想到,每次要扔掉一個數組或兩個陣列中的k/2個數,於是想到去比較A[k/2]和B[k/2],仔細思考比較結果,然後想到較大的那一方的前k/2個數一定都在前k-1大的數中,於是可以扔掉。

進階問題的考察點是逆向思維。二分答案是一種常見的演算法思路(見面試題2 抄書問題),所以當你想不出題目的時候,往往可以試試看是否可以二分答案。因為需要發揮N臺機器的平行計算能力,所以想到讓每臺機器互不相關的做一件事情,然後將結果彙總來判斷。

一般來講,面試中問題這兩個題目,說明職位對演算法能力的要求還是比較高的。

面試題9 前k大的和

初階:有兩個陣列A和B,每個陣列有k個數,從兩個陣列中各取一個數加起來可以組成k*k個和,求這些和中的前k大。

進階:有N個數組,每個陣列有k個數,從N個數組中各取一個數加起來可以組成k^n個和,求這些和中的前k大。

答:

初階:定義C[i][j] = A[i]+B[j],假設A,B從大到小排序,那麼C[0][0]為最大的和。

A/B

8

6

4

2

7

15

13

11

9

5

13

11

9

7

3

11

9

7

5

1

9

7

5

3

將C[0][0]拿走以後,C[1][0]和C[0][1]則都可能成為第二個最大和。設定可能成為最大和的集合S,一開始S={C[0][0]},每次從集合中選一個最大的數C[i][j]出來,然後將C[i+1][j]和C[i][j+1]加入到集合中(如果有的話)。直到選足k個數。由於同時可能在集合中存在的元素只有n個(每行每列會同時有一個數在集合中),採用堆來實現該集合,每次找一個最大數複雜度O(logn),總時間複雜度O(nlogn)

進階:先對前兩個陣列求前k大和,將結果與第三個陣列求前k大和,然後第四個……直到第N個。

面試官角度:

初階問題的難度是需要將所有的和構造成一個矩陣的形式來思考。然後考察基本的資料結構堆的使用。

進階問題中,考察的是如何將一個複雜的問題化簡為一個我們已經知道解法的問題。我們可以解決2個數組求前k大和的問題了,那麼N個數組的情況,就想辦法變為2個數組的情況就可解了。

面試題10 賽馬問題

有25匹馬,有一個5個賽道的馬場,每場比賽可以決出5匹馬的排名,假設每匹馬發揮穩定,且不會出現名次相同的情況。問,如果要知道25匹馬中跑得最快的馬,需要幾場比賽?如果需要知道跑得第二快的馬,需要幾場比賽?第三快的呢?
答:

最快需要6場。第二快7場。第三塊7場。

25匹馬分5組比賽5次,可得到各個組內的排名。將5個第一名再賽一次,就可以知道25匹馬中最快的馬。將最快的馬那組的第二名替換掉第一名,再比一次,就可以知道第二快的馬是誰。

對於第三快的馬。我們先構造“遞增矩陣”:


遞增矩陣的意義在於,第一名一定是左上角的那匹馬。去掉左上角之後,第二名就是在兩個2之間選擇。再進一步可以推出,可能成為第三名的一共有4個位置,上圖中已用紅色的數字標出。因此,將這4匹馬拿出來再比一次即可。

面試官角度:

這個題目一方面是考察聰明程度的智力題,另一方面,如果拒賠遞增矩陣的一些知識,對該題的解答也是有一定幫助的。遞增矩陣是指每一行和每一列的數均從小到大排列的矩陣。

面試題11 遞增矩陣

遞增矩陣是指每一行和每一列均從小到大排列矩陣。給你一個遞增矩陣A和整數x,設計一個演算法在A中查詢x,找不到返回無解。

答:

從矩陣的右上角出發(左下角同理),如果該數<x,則往下走;如果>x,則往左走。時間複雜度O(n)。

面試官視角:

如果是一個有序的陣列中找一個數,那麼自然是用O(logn)的二分法。升級為有序矩陣之後,自然也容易想到二分法。但進一步想會發現,如果從矩陣中間選擇一個數,每次只能去掉1/4,而且破壞了矩陣的形狀,無法進行遞迴。因此二分的思路就變得不可行了。從而將複雜度提高一點想一想O(n)的方法,思路上仍然是根據與x比較大小來決定扔掉一些數,於是中間不行,就嘗試4個角,從而發現可以從右上角出發來進行查詢。

面試題12 最大子區間/矩陣

初階:陣列A中有N個數,需要找到A的最大子區間,使得區間內的數和最大。即找到0<=i<=j<N,使得A[i]+A[i+1] … + A[j]最大。A中元素有正有負。

進階:矩陣A中有N*N個數,需要找到A的最大的子矩陣。

答:

初階:設Sum[i] = 前i個數的和,Min[i] = min{Sum[1], Sum[2] … Sum[i-1]}。從左到右列舉i,計算Sum[i]-Min[i]的最大值,即為答案。時間複雜度O(n),空間複雜度O(1),只需要在列舉的過程中記錄一個Sum,一個Min和一個全域性答案的Max即可。

進階:列舉最大子矩陣的上下邊界x和y,將第x行到第y行每一列的數疊加成為一個數。然後就成為了一個初階的問題。時間複雜度O(n^3)。

面試官角度:

初階問題有若干種解法,上面給出的是列舉的方法。一種貪心的方法是,累加Sum的過程中,如果Sum<0,就讓Sum=0(意味著之前的數不如不取,所以全部扔掉)。進階的問題主要是考察是否能將其簡化為初階的問題來解決。其中進階問題一般會考察程式實現,需要進行練習。

面試題13 隨機數生成器

有一個隨機數生成器,每次等概率的返回1到5中的一個整數。現在需要你利用這個隨機數生成器來設計一個新的隨機數生成器,每次可以等概率的返回1到7中的一個整數。

答:

隨機兩次rand(5)相當於隨機一次rand(25),將前21個數三三一組分為7組,如果得到的數<=21,則返回對應組號; 如果>21則重複上述過程,直到得到的數<=21。

時間複雜度為O(2*21/25 + 4 * 21/25 * 4/25 + 6 * 21/25 * 4/25 * 4/25 … ) = O(1)

面試官角度:

這個題目的解題思路有一點智力題的感覺。因為5和7互質,所以無法找到5^n被7整出。一個基本的陷阱是,呼叫兩次rand(5)得到的是rand(25)而不是rand(10)。

面試題14 超過一半的數

初階:有N個數,其中一個數的出現次數嚴格超過了一半。求這個數。

進階1:有N個數,其中兩個數的出現次數都超過了⅓ ,求這兩個數。

進階2:有N個數,其中一個數的出現次數嚴格超過了⅓,並且沒有第二個這樣的數。求這個數。

以上兩問均要求O(n)的時間複雜度和O(1)的額外空間複雜度。

答:

初階:抵消法。如果兩個數不一樣,扔掉這兩個數,剩下來的數中,要找的數的出現次數仍然會超過一半。所以整個過程中只需要儲存一個數,及其出現次數即可。

進階1:仍然是抵消法。如果三個數不一樣,就三個數都扔掉。因此記錄2個數,及其各自出現次數即可。

進階2:沿用進階1的演算法。如果最後剩下1個數,那麼就是答案了;如果剩下2個數,重新掃描這N個數,統計這兩個數的出現次數則可以得到答案。

面試官視角:

初階問題是著名的晶片測試問題的另一個版本。一般來講大學的演算法課上都會講到。主要考察的是演算法基本功底。進階1和進階2都是需要想辦法利用初階問題的思路去解決,如果只是背下了初階問題的解答沒有真正理解,進階問題就無法回答出來。進階2在回答的時候需要和麵試官溝通是否可以再掃描一遍陣列。

面試題15 字串編輯距離

有兩個字串A和B,對A可以進行如下的操作:插入一個字元,刪除一個字元,替換一個字元。問A可以通過最少多少次操作變為B?我們定義這個結果為字串的最小編輯距離。

答:

動態規劃。設f[i][j]代表A的i個字元與B的前j個字元完美匹配上時,需要的最小操作次數。有狀態轉移方程如下:

f[i][j] = max{f[i-1][j] + 1, f[i][j-1] + 1, f[i-1][j-1] + 1} // if A[i] != B[j]

       = max{f[i-1][j] + 1, f[i][j-1] + 1, f[i-1][j-1]} // if A[i] == B[j]

答案為f[A.length][B.length]。時間複雜度O(n^2)。

面試官角度:

字串編輯距離是經典的動態規劃問題,一般來說,這個題目還會要求實現。讀者可以嘗試自己寫寫看。寫動態規劃時需要注意的地方有:初始化,迴圈邊界。一個類似思路的題目有:最長公共子序列。

面試題16 01隨機生成函式

有一個01隨機生成函式,rand(2),以p的概率生成1,1-p的概率生成0。請用這個生成函式設計一個等概率的01隨機生成函式。

答:

隨機2次,可能的結果有,00, 01, 10, 11。概率分別為:(1-p)*(1-p), (1-p)*p, p*(1-p), p*p。可以發現01和10的生成概率是相等的。因此讓01代表0,10代表1,如果隨機出了00或者11,就再隨機2次。

面試官視角:

本題和九章演算法面試題13都是經典的隨機數生成函式的題目。他們用到的一個基本思路通過多次隨機構造答案所需要的等概率事件,該事件可能是生成結果的一個子集,在子集以外的結果,就重新來一次。

#隨機# #概率#

面試題17 從輸入流中隨機取記錄

有一個很大很大的輸入流,大到沒有儲存器可以將其儲存下來,而且只輸入一次,如何從這個輸入流中等概率隨機取得m個記錄。

答:

開闢一塊容納m個記錄的記憶體區域,對於資料流的第n個記錄,以m/n的概率將其留下(前m個先存入記憶體中,從第m+1個開始),隨機替換m個已存在的記錄中的一個,這樣可以保證每個記錄的最終被選取的概率都是相等的。

面試官視角:

這個題目除了需要給出正確解答以外,還需要證明你的解答。考察的是對概率隨機問題的掌握情況和歸納法的運用。下面給出一個簡單的證明:

設資料流中已經有n個記錄流過,在記憶體中的m個記錄中,假設都是等概率取得的,每個數命中的概率都為:mn。對於第n+1個記錄,以mn+1的概率選中,如果沒有選中,則記憶體中的m個記錄均被留下來,每個數留下來其概率為:mn * (1-mn+1) = m(n+1-m)n(n+1);如果選中,新留下來的數概率自然是mn+1,而原來記憶體中的m個數中留下來m-1個數,每個數留下來的概率是:mn*mn+1*m-1m = m(m-1)n(n+1)。兩種情況下概率之和為m(m-1)n(n+1)+m(n+1-m)n(n+1)=mn+1,即為原來被選中數,繼續被選中的概率。由此我們不難得出,記憶體中每個數被選中概率一直都是mn。

面試題18 複製連結串列

初階:複製一個簡單鏈表。假設連結串列節點只包含data和next。

進階:假設連結串列節點新增一個屬性叫random,他隨機指向了連結串列中的任何一個節點。複製這個連結串列。

答:

初階:程式設計實現(略)。

進階:將1->2->3->4->NULL先複製為1->1->2->2->3->3->4->4->NULL,然後再拆開。

面試官角度:

連結串列複製是考察對指標運用的熟練程度。對於初階和進階的問題都會要求實現。關鍵點並不在於想出進階問題怎麼做,而是一定要把實現寫好。對於進階問題的做法如何想到,就看你聰不聰明或者是不是準備過這個題目了。

面試題19 最常訪問IP

給你一個海量的日誌資料,提取出某日訪問網站次數最多的IP地址。

答:

將日誌檔案劃分成適度大小的M份存放到處理節點。

每個map節點所完成的工作:統計訪問百度的ip的出現頻度(比較像統計詞頻,使用字典樹),並完成相同ip的合併(combine)。

map節點將其計算的中間結果partition到R個區域,並告知master儲存位置,所有map節點工作完成之後,reduce節點首先讀入資料,然後以中間結果的key排序,對於相同key的中間結果呼叫使用者的reduce函式,輸出。

掃描reduce節點輸出的R個檔案一遍,可獲得訪問網站度次數最多的ip。

面試官角度:

該問題是經典的Map-Reduce問題。一般除了問最常訪問,還可能會問最常訪問的K個IP。一般來說,遇到這個問題要先回答Map-Reduce的解法。因為這是最常見的解法也是一般面試官的考點。如果面試官水平高一點,要進一步問你有沒有其他解法的話,該問題有很多概率演算法。能夠在極少的空間複雜度內,掃描一遍log即可得到Top k Frequent Items(在一定的概率內)。有興趣的讀者,可以搜搜“Sticky Sampling”,”Lossy Counting”這兩個演算法。

參考:http://www.ninechapter.com/category/problem_solving/