編程之美:常見面試題思想方法整理


對於很多精妙的題目,常常在想這些出題的童鞋是怎么想到的,因為這些題目確實能夠很深入的考察出一個面試者的邏輯,算法和思維功底。本文章主要是總結自己在做這些面試題和了解相應解法的過程中思考的一些東西,感覺這些東西在很多題目中都出現過,非常值得抽象出來專門理解。

  一 雙指針遍歷

  所謂雙指針,是利用兩個指針對一個有序數組進行遍歷,查找出符合要求的數據集合。相信大家都接觸到了這種思維模式的解題方法,只是沒有注意到罷了。下面舉幾個例子吧。

  例1:給定一個數組a[n],求數組中是否存在兩個數的和等於給定值sum並輸出?

編程之美 2.12 快速尋找滿足條件的兩個數

  這個問題很常見,我當年在面試微軟實習生的時候就被問到了此題,解決方法有很多種,這里我就不贅述,我講的是用雙指針遍歷法的。首先數組不一定有序,對數組排序是必須的。那么便來到了這樣一個場景:對有序數組如何遍歷來求得符合要求的數據集合?雙指針的解決方法如下:定義兩個指針(i 和 j),分別指向數組頭和尾,那么會出現如下三種情況:

  1. 如果a[i]+a[j] == sum,那么很顯然,只要輸出這兩個數,並把指針i+1和j-1指向下一個數即可。(這里不輸出重復的組合)
  2. 如果a[i]+a[j] > sum,說明當前遍歷的數值偏大,所以可以把j-1以減小和的值,在繼續比較。
  3. 如果a[i]+a[j] < sum,說明當前遍歷的數值偏小,同樣為了加大和可以把i+1。

總的時間復雜度取決於排序即O(nlogn)。

例2:這題來自編程之美2.21只考加法的面試題,原題大致意思是寫一個程序,對於一個32位整數,輸出它所有可能的連續自然數之和的算式,要求是這些連續自然數之和要等於原數。例如3 = 2+1; 9 = 4+5,9 = 2+3+4等。

這題有兩種解法, 其中一種便是雙指針法,還有一種比較巧妙,是我同學在面試阿里雲計算的時候想到的,利用了數學方法,簡單來說是求出一個公式來。這里只說雙指針的解法。

這里需要一個轉化,把求n中所有可能的連續自然數之和歸約為在數組{1,2,3,...,n}中找所有連續子序列和等於n的問題。這里同樣也是這樣一個場景:對有序數組如何遍歷來求得符合要求的數據集合?這時的雙指針可以不是一頭一尾了,而是兩個都指向頭部,這樣可以以高效的順序遍歷我們要找的所有集合。初始設i=j=1,這里同樣會出現三種情況:

  1. sum[i,j] == sum, 直接輸出i到j的值,並把i+1,j+1,因為只是i+1肯定是不等的,因為和小了,同樣j+1只會使和變大,所以兩個都要往前加(注意這里指針不用考慮減小,因為這在以前就考慮過了)
  2. sum[i,j] < sum,說明偏小,那么提高j來使得和變大才有可能相等
  3. sum[i,j] > sum,說明偏大,那么提高i來使得和變小才有可能相等

這樣,代碼就出來了:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<span style= "font-size: 16px;" > //計算連續和為n的所有子序列
void GetAnswer( int n)
{
     int i=1;
     int j=1;
 
     while (i<=n/2 && j <= n)
     {
         
         int sum = (j+i)*(j-i+1)/2;
 
         if (sum == n)
         {
             for ( int k=i; k <= j; k++)
                 cout<<k<< " " ;
             cout<<endl;
             j++;
             i++;
         }
         else if (sum < n)
         { //sum[i..j]<n,只能提高j以增大sum
             j++;
         }
         else //sum[i..j]>n,只能提高i以減小sum
             i++;
     }
}
</span>

這里的思想本質上與上面例題1是相同的,這也是我抽象出這種思維模型的原因,當遇到有序數組或者歸約到有序數組時,利用雙指針遍歷的方法是求得我們需要的數據集合的一種相對比較高效的方法。

二 排除以減少解空間大小

相信這種方法大家都聽過,但是實際使用的時候卻時常忘了去考慮這種思維模式,我這里舉的都是很巧妙的例子,也是我遇到的,感覺絕對值得把這種思考方法總結出來。

 例1 此種解法很值得一說的題目來自編程之美2.3 尋找發帖水王,大致意思是:

 某論壇有一個“水王”,經常發帖,據說該“水王”發帖數目超過了帖子總數的一半,那么如何在id發帖列表中快速的查找到這一“水王”?

這道題非常好的體現了排除法的非凡效果,如果直接去求這個水王,方法也不少,例如按發帖數排序,但是這至少是O(nlogn)時間復雜度,實際上最好的算法卻是盡可能的去減少解空間,把不可能的去除掉,留下的自然是要求的的解。對於這道題,就是每次去除兩個不同id的發帖,由於默認水王發帖超過一半,那么去除任何兩個不同id后仍然是超過一半的,why?可以這樣想,開始滿足的公式是 x>y/2,那么減去兩個不同id后,最壞情況,這兩個不同id中有一個是水王的id,則(x-1)/(y-2) > (y/2-1)/(y-2) > 1/2,即仍然是大於二分之一的, 所以可以不斷的這樣做直到最終剩下水王id為止。編程之美上給出了一個非常精妙的程序,這里不贅述。

 相同的例子還有這里

實際上這類思想的應用場景可以認為如果看到要求的東西占總體數量一半以上情況的時候,可以考慮排除法。當然還有其他情況,例如正面求解屢試不行的時候,也可以考慮這樣的方法。

三 蓄水池抽樣求概率模型

想起這個是因為多次碰到類似概率題要用到它,例如10月16號百度北京的筆試題中有它,然后同學面試阿里雲被問到的題目中有它,我發現不僅僅是當不知道n多大的時候,即便有時候知道n多大,也可以使用這個模型,詳細的關於模型的知識可以查看wiki也看以看我這篇文章

我同學在面試阿里雲的時候被問到這樣一個概率的問題:

給你一個n個長度的鏈表,以及一個函數,這個函數50%的概率返回0,50%的概率則返回1,問如何用這些條件從這n個鏈表中隨機的抽取k個節點。

利用蓄水池抽樣可以有這樣一個解法,首先,這個函數可以產生0和1,那么我們可以通過構造多個二進制位(調用多次這個函數)並只取其中某些情況來構造任意概率,例如假設我要構造1/4,那么我調用兩次這個函數,如果出現00,我認為發生,如果不是則不發生;又假設要構造3/5,我調用三次這個函數,並假定3種出現為無效出現,例如000,001,010。如果出現這種則再次調用函數生成,同時我們指定某三種為出現,其余不出現,這樣可以構造一個生成概率3/5的生成器。有了這個,我們利用蓄水池抽樣思想,先指定前k個節點為所求,並把指針指向第k+1個節點,此時以k/(k+1)決定是否與前k個選定的節點替換,替換時隨機選擇,並以此類推直至結尾。最后剩余的節點即為所求。

 四:動態規划:

編程之美:2.14 求數組的子數組之和的最大值和最小值(動態規划)

五。分而治之:

編程之美的2.17,數組循環移位 & 字符串逆轉 Hello world => world Hello

總的來說,我覺得這三個思想都是經常可以利用的,有些問題沒有見過這些思想是挺難在面試時當場想出來的,相反如果通過面試題提取出某些抽象可復用的思想,那么以后任何變形的面試題,都可以歸約至這些解法從而解決,相信那個時候面試官一定會對你刮目相看。


免責聲明!

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



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