經典基礎算法之面試題(系列一)


1. 打靶問題的遞歸解法

倫敦奧運會火熱進行中,讓我們來看個打靶的問題:一個射擊運動員打靶,靶一共有10環,求連開10槍打中90環的可能行有多少種?
分析:這是一個典型遞歸求解問題。假設第10槍打x環,則將問題轉換為剩下9槍打90-x環的可能有多少種,x的取值范圍為[0, 10],根據加法原理,則:10槍打90環的可能 = 第10槍打0環,剩下9槍打90環的可能 + 第10槍打1環,剩下9槍打89環的可能 + 第10槍打2環,剩下9槍打88環的可能
+ 第10槍打3環,剩下9槍打87環的可能 + 第10槍打4環,剩下9槍打86環的可能 + 第10槍打5環,剩下9槍打85環的可能
+ 第10槍打6環,剩下9槍打84環的可能 + 第10槍打7環,剩下9槍打83環的可能 + 第10槍打8環,剩下9槍打82環的可能
+ 第10槍打9環,剩下9槍打81環的可能 + 第10槍打10環,剩下9槍打80環的可能。

image

遞歸的停止條件為:
1. 若環數小於0 或者 剩下的環數大於剩下的槍數乘以10(即剩下每槍打10環用不玩所剩環數),則該遞歸路徑不記入可能情況。
2. 若不滿足條件1,且所剩槍數為1,則該遞歸路徑記為1中可能情況。

 1:  #include <stdio.h>
 2:  
 3:  int recursion(int count, int score)
 4:  {
 5:      if (score < 0 || score > count * 10) {
 6:          return 0;
 7:      }
 8:   
 9:      if (1 ==  count) {
10:          return 1;
11:      }    
12:   
13:      int i = 0;
14:      int sum = 0;
15:   
16:      for (; i <= 10; ++i) {
17:          sum += recursion(count - 1, score - i);
18:      }
19:   
20:      return sum;
21:  }
22:   
23:  int main()
24:  {
25:      printf("result: %d\n", recursion(10, 90));        
26:      return 0;
27:  }

 

2. 求多項式的值與求冪的快速算法

2.1 求多項式的值

已知多項式中的系數a0,a1,a2……an及x的值,求f(x)。
image
多項式求值問題可以利用輾轉相乘的方式進行計算,根據上面的式子分析,可以將上式轉換為: f(x) = ((((an * x) + an-1) * x + an-2) * x  ……. + a1) * x + a0
有了這個變形,代碼自然而然就能寫出來了:

 1:  #include <stdio.h>
 2:  
 3:  int polynomial(const int *coefficient, int n, int x)
 4:  {
 5:      int sum = 0;
 6:   
 7:      int i = n - 1;
 8:      for (; i != 0; --i) {
 9:          sum = sum * x + coefficient[i];
10:      }
11:   
12:      return sum * x + coefficient[0];
13:  }
14:   
15:  int main()
16:  {
17:      int coeff[5] = {1, 2, 3, 4, 5};
18:      printf("result: %d\n", polynomial(coeff, sizeof(coeff) / sizeof(coeff[0]), 10));
19:      return 0;
20:  }

 

2.2 求冪的快速算法

求x的p次冪本是簡單的問題,可以將x次乘p次就可以了,這里我們當然不是要討論這種計算方法,這里討論的是怎樣高效計算。
有沒有可能減少做乘法的次數呢?讓我們來做一個分析,考慮x的6次冪的情況:x * x * x * x * x * x = (x * x * x) * (x * x * x)
等式前面部分需進行6次乘法,后半部分需計算(x * x * x) ,然后乘以上次計算的值即可,共4次乘法。
通過上述分析可知,求x的p次冪可以通過遞歸折半的方法來減少乘法的次數,循着這個思考,實現就不太困難了。

 1:  #include <stdio.h>
 2:  
 3:  /* 統計乘法的次數 */
 4:  static int multiply_count = 0;
 5:   
 6:  /* 求x的p次冪 */
 7:  int power(int x, int p)
 8:  {
 9:      int ret =  0;
10:   
11:      if (1 == p) {
12:          return x;
13:      }
14:   
15:      /* 先計算x的p/2次冪 */
16:  ret = power(x, p >> 1);
17:      ret *= ret;
18:      ++multiply_count;
19:   
20:      /* 若p是奇數,則再乘一次x */
21:  if (0 != p % 2 /* 求模本身是一個耗性能的運算,這里可優化為 p – ((p >> 1) << 1) */) { 
22:          ret *= x;
23:          ++multiply_count;
24:      }    
25:   
26:      return ret;
27:  }
28:   
29:  int main()
30:  {
31:      printf("resulut: %d\n", power(2, 20));    
32:      printf("multiply count: %d\n", multiply_count);
33:      return 0;
34:  }

計算2的20次冪總共進行5次乘法就足夠了,而原始的算法需要20次,那么這種算法究竟需要多少次乘法呢?
由於該算法根本思想是折半遞歸,類似於2分查找,所以乘法的次數為lgp取天花板值這個數量級的(lgp表示以2為底p的對數)。
整型int最多表示2的32次冪,因此最多節省24次乘法,對於現代的計算機,這似乎不是特別重要,但是在以下情況下該算法具有重要價值:
1. 對於需要高頻率計算冪的情況;
2. 對於大數高精度計算的情況,如需計算2的10000次冪。
當然了,這也是對遞歸算法和2分法的巧妙應用,學習其思想吧。

3. 一年中的第n天是幾月幾號?

和這個問題類似的問題還有:
1. 給定某年某月某日,問這是這年的第多少天?
2. 已知某年的1月1號是星期幾,求給定的某年某月某日是星期幾?

其實這類問題的共同點在於它們都需要考慮閏年問題,大月小月問題;
首先我們解決閏年問題,根據閏年的定義定義如下宏來判斷某年是否是閏年:
#define IS_LEAP(X) (((X) % 400 == 0 || (X) % 100 != 0 && (X) % 4 == 0) ? 1 : 0)

在來看大月小月問題,可以定義如下二維數組,用day_count_of_month[0][12]表示潤年情況下每月的天數,用day_count_of_month[1][12]表示非潤年情況下每月的天數(這就是傳說中的字典法了,就是根據提供的信息查表,類似於查字典,所以叫做字典法):
int day_count_of_month[2][12] = {

                                                {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
                                                {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}

                                               };

 1:  #include <stdio.h> 
 2:  
 3:  #define IS_LEAP(X) (((X) % 400 == 0 || (X) % 100 != 0 && (X) % 4 == 0) ? 1 : 0)
 4:  int day_count_of_month[2][12] = {{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, 
 5:                                   {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} }; 
 6:  /* 計算year年的第n天是幾月幾號 */
 7:  void convert_to_date(int year, int n) 
 8:  { 
 9:      int leap = IS_LEAP(year); 
10:      if(year < 1970 || year > 5000 || n <= 0 || n > 365 + leap) { 
11:          printf("bad year\n"); 
12:      }
13:   
14:      int i = 0; 
15:      for(; n > day_count_of_month[leap][i]; ++i) { 
16:          n -= day_count_of_month[leap][i]; 
17:      } 
18:   
19:      printf("year : %d, month : %d, day : %d\n", year, i + 1, n); 
20:  } 
21:   
22:  int main() 
23:  { 
24:      convert_to_date(2012, 230); 
25:  } 

上面這段代碼解決了第一個問題,第二個問題可以轉換為類似第一個問題的問題:
先計算給定的某年某月某日到給定已知這天的總天數s=>s%7=>利用模運算結果來推算所求日期的星期情況即可。

點擊這里獲取本文相關源碼。 

本文許可自由轉載,轉載請注明出處!


免責聲明!

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



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