互聯網公司常見面試算法題

1、假設淘寶一天有5億條成交數據,求出銷量最高的100個商品並給出算法的時間複雜度。

先用哈希,統計每個商品的成交次數,然後再用在N個數中找出前K大個數的方法找出成交次數最多的前100個商品。
優化方法:可以把5億個數據分組存放,比如放在5000個文件中。這樣就可以分別在每個文件的10^6個數據中,用哈希+堆統計每個區域內前100個頻率最高的商品,最後求出所有記錄中出現頻率最高的前100個商品。


2、有10億個雜亂無章的數,怎樣最快地求出其中前1000大的數。

方法一: 建一個1000個數的最小堆,然後依次添加剩餘元素,如果大於堆頂的數(堆中最小的),將這個數替換堆頂,並調整結構使之仍然是一個最小堆,這樣,遍歷完後,堆中的1000個數就是所需的最大的1000個。算法的時間複雜度爲O(nlogk)=n*log1000=10n(n爲10億,k爲1000)。
優化的方法:分治法。可以把所有10億個數據分組存放,比如分別放在1000個文件中。這樣處理就可以分別在每個文件的10^6個數據中找出最大的10000個數,合併到一起再找出最終的結果。
優化的方法:如果這10億個數裏面有很多重複的數,先通過Hash法,把這10億個數字去重複,這樣如果重複率很高的話,會減少很大的內存用量,從而縮小運算空間,然後通過分治法或最小堆法查找最大的1000個數。
方法二:
1.用每一個BIT標識一個整數的存在與否,這樣一個字節可以標識8個整數的存在與否,對於所有32位的整數,需要512Mb,所以開闢一個512Mb的字符數組A,初始全0
   2.依次讀取每個數n,對於數n,n存放在A[N>>3]中的某個bit,因此,將A[n>>3]設置爲A[n>>3]|(1<<(n%8))(這裏是1左移幾位),相當於將每個數的對應位設置爲1
   3.在A中,從數組尾開始向前遍歷,從大到小讀取1000個值爲1的數,就是最大的1000個數了。
這樣讀文件就只需要1遍,在不考慮內存開銷的情況下,應該是速度最快的方法了。

2、給一列無序數組,求出中位數並給出算法的時間複雜度。

若數組有奇數個元素,中位數是a[(n-1)/2];若數組有偶數個元素,中位數爲a[n/2-1]和a[n/2]兩個數的平均值。這裏爲方便起見,假設數組爲奇數個元素。

思路一:把無序數組排好序,取出中間的元素。時間複雜度取決於排序算法,最快是快速排序,O(nlogn),或者是非比較的基數排序,時間爲O(n),空間爲O(n)。這明顯不是我們想要的。

思路二:採用快速排序的分治partition過程。任意挑一個元素,以該元素爲支點,將數組分成兩部分,左邊是小於等於支點的,右邊是大於支點的。如果左側長度正好是(n-1)/2,那麼支點恰爲中位數。如果左側長度<(n-1)/2, 那麼中位數在右側,反之,中位數在左側。 進入相應的一側繼續尋找中位數。

  1. //快速排序的分治過程找無序數組的中位數  
  2. int partition(int a[], int low, int high) //快排的一次排序過程  
  3. {  
  4.     int q = a[low];  
  5.     while (low < high)  
  6.     {  
  7.         while (low < high && a[high] >= q)  
  8.             high--;  
  9.         a[low] = a[high];  
  10.         while (low < high && a[low] <= q)  
  11.             low++;  
  12.         a[high] = a[low];  
  13.     }  
  14.     a[low] = q;  
  15.     return low;  
  16. }  
  17. int findMidium(int a[], int n)  
  18. {  
  19.     int index = n / 2;  
  20.     int left = 0;  
  21.     int right = n - 1;  
  22.     int q = -1;  
  23.     while (index != q)  
  24.     {  
  25.         q = partition(a, left, right);  
  26.         if (q < index)  
  27.             left = q + 1;  
  28.         else if (q>index)  
  29.             right = q - 1;  
  30.     }  
  31.     return a[index];  
  32. }  

思路三:將數組的前(n+1)/2個元素建立一個最小堆。然後,對於下一個元素,和堆頂的元素比較,如果小於等於,丟棄之,如果大於,則用該元素取代堆頂,再調整堆,接着看下一個元素。重複這個步驟,直到數組爲空。當數組都遍歷完了,(堆中元素爲最大的(n+1)/2個元素,)堆頂的元素即是中位數。

  1. //構建最小堆找無序數組的中位數  
  2. void nswap(int& i, int& j)  
  3. {  
  4.     i = i^j;  
  5.     j = i^j;  
  6.     i = i^j;  
  7. }  
  8. void minHeapify(int a[], int i, int len)  
  9. {  
  10.     int temp;  
  11.     int least = i;  
  12.     int l = i * 2 + 1;  
  13.     int r = i * 2 + 2;  
  14.     if (l < len && a[l] < a[least])  
  15.         least = l;  
  16.     if (r < len && a[r] < a[least])  
  17.         least = r;  
  18.     if (least != i)  
  19.     {  
  20.         nswap(a[i], a[least]);  
  21.         minHeapify(a, least, len);  
  22.     }  
  23. }  
  24. void buildMinHeap(int a[], int len)  
  25. {  
  26.     for (int i = (len-2) / 2; i >= 0; i--)  
  27.     {  
  28.         minHeapify(a, i, len);  
  29.     }  
  30. }  
  31. int findMidium2(int a[], int n)  
  32. {  
  33.     buildMinHeap(a, (n + 1) / 2);  
  34.     for (int i = (n + 1) / 2; i < n; i++)  
  35.     {  
  36.         if (a[i] > a[0])  
  37.         {  
  38.             nswap(a[i], a[0]);  
  39.             minHeapify(a, 0,(n + 1) / 2);  
  40.         }         
  41.     }  
  42.     return a[0];  
  43. }  

引申一:
查找N個元素中的第K個小的元素

編程珠璣給出了一個時間複雜度O(N)的解決方案。該方案改編自快速排序。
經過快排的一次劃分,
   1)如果左半部份的長度>K-1,那麼這個元素就肯定在左半部份了
   2)如果左半部份的長度==K-1,那麼當前劃分元素就是結果了。
   3)如果。。。。。。。<K-1,那麼這個元素就肯定在右半部分了。
  並且,該方法可以用尾遞歸實現。效率更高。

也可以用來查找N個元素中的前K個小的元素,前K個大的元素。。。。等等。


引申二:
查找N個元素中的第K個小的元素,假設內存受限,僅能容下K/4個元素。
分趟查找,
第一趟,用堆方法查找最小的K/4個小的元素,同時記錄剩下的N-K/4個元素到外部文件。
第二趟,用堆方法從第一趟篩選出的N-K/4個元素中查找K/4個小的元素,同時記錄剩下的N-K/2個元素到外部文件。
。。。
第四趟,用堆方法從第一趟篩選出的N-K/3個元素中查找K/4個小的元素,這是的第K/4小的元素即使所求。


3、輸入一個整型數組,求出子數組和的最大值,並給出算法的時間複雜度。

設b[i]表示a[0...i]的子數組和的最大值,且b[i]一定包含a[i],即:

sum爲子問題的最優解,

1. 包含a[i],即求b[i]的最大值,在計算b[i]時,可以考慮以下兩種情況,因爲a[i]要求一定包含在內,所以

     1) 當b[i-1]>0, b[i] = b[i-1]+a[i]

     2) 當b[i-1]<=0, b[i] = a[i], 當b[i-1]<=0,這時候以a[i]重新作爲b[i]的起點。     

2. 不包含a[i],即a[0]~a[i-1]的最大值(即0~i-1局部問題的最優解),設爲sum

最後比較b[i]和 sum,即,如果b[i] >sum ,即b[i]爲最優解,然後更新sum的值.

在實現時,bMax代表 b[k], sum更新前代表前一步子問題的最優解,更新後代表當前問題的最優解。實現如下:

  1. //求數組的子數組和的最大值,時間複雜度爲O(n)  
  2. int maxSumArr(int a[], int n,int* start, int* end)  
  3. {  
  4.     int s, e;  
  5.     int sum = a[0];  
  6.     int bMax=a[0];  
  7.         *start = *end = 0;  
  8.     for (int i = 1; i < n; i++)  
  9.     {  
  10.         if (bMax > 0) //情況一,子數組包含a[i],且b[i-1]>0(上一次的最優解大於0),b[i] = b[i-1]+a[i]  
  11.         {  
  12.             bMax += a[i];  
  13.             e = i;  
  14.         }             
  15.         else     //情況二,子數組包含a[i],且b[i-1]<=0(上一次的最優解小於0),這時候以a[i]重新作爲b[i]的起點。  
  16.         {  
  17.             bMax = a[i];  
  18.             s = i;  
  19.             e = i;  
  20.         }            //情況三,子數組不包含a[i],即b[i]=sum  
  21.         if (bMax > sum)   //三種情況相比較,最大值作爲更新後的最優解,存在sum  
  22.         {  
  23.             sum = bMax;  
  24.             *start = s;  
  25.             *end = e;  
  26.         }  
  27.     }  
  28.     return sum;  
  29. }  

引申:求子數組和的最小值

同理。

  1. //求數組的子數組和的最小值,時間複雜度爲O(n)  
  2. int minSumArr(int a[], int n, int* start, int* end)  
  3. {  
  4.     int s, e;  
  5.     int bMin = a[0];  
  6.     int sum = a[0];  
  7.     *start = *end = 0;  
  8.       
  9.     for (int i = 0; i < n; i++)  
  10.     {  
  11.         if (bMin < 0) //情況一,子數組包含a[i], 且b[i-1]<0,b[i] = b[i-1]+a[i]  
  12.         {  
  13.             bMin += a[i];  
  14.             e = i;  
  15.         }  
  16.         else  //情況二,子數組包含a[i],且b[i-1] > 0,這時候以a[i]重新作爲b[i]的起點  
  17.         {  
  18.             bMin = a[i];  
  19.             s = e = i;  
  20.         }      //情況三,子數組不包含a[i],即b[i]=sum  
  21.         if (bMin < sum)  //三種情況相比較,最小值作爲更新後的最優解,存在sum  
  22.         {  
  23.             sum = bMin;  
  24.             *start = s;  
  25.             *end = e;  
  26.         }  
  27.     }  
  28.     return sum;  
  29. }  

4、給出10W條人和人之間的朋友關係,求出這些朋友關係中有多少個朋友圈(如A-B、B-C、D-E、E-F,這4對關係中存在兩個朋友圈),並給出算法的時間複雜度。

  1. //朋友圈-並查集  
  2. int set[10001];  
  3. int find(int x)  
  4. {  
  5.     int i, j, r;  
  6.     r = x;  
  7.     while (set[r] != r) //尋找此集合的代表  
  8.         r = set[r];  
  9.     i = x;  
  10.     while (i != r) //使得r代表的集合中,所有結點直接指向r,即路徑壓縮  
  11.     {  
  12.         j = set[i];  
  13.         set[i] = r;  
  14.         i = j;  
  15.     }  
  16.     return r;  
  17. }  
  18. void merge(int x, int y)  
  19. {  
  20.     int t = find(x);  
  21.     int h = find(y);  
  22.     if (t < h)  
  23.         set[h] = t;  
  24.     else  
  25.         set[t] = h;  
  26. }  
  27. int friends(int n, int m, int (*r)[2])  //n個人,m對好友關係,存放在二維數組r[m][2]中  
  28. {  
  29.     int i, count;  
  30.     for (i = 1; i <= n; i++)  
  31.         set[i] = i;  
  32.     for (i = 0; i < m; i++)  
  33.         merge(r[i][0], r[i][1]);  
  34.     count = 0;  
  35.     for (i = 1; i <= n; i++)  
  36.     {  
  37.         if (set[i] == i)  
  38.             count++;  
  39.     }  
  40.     return count;  
  41. }  

5、如圖所示的數字三角形,從頂部出發,在每一結點可以選擇向左走或得向右走,一直走到底層,要求找出一條路徑,使路徑上的值的和最大。給出算法的時間複雜度。



定義狀態爲:dp[i][j]表示,從第i行第j個數字到最後一行的某個數字的權值最大的和。那麼我們最後只需要輸出dp[1][1]就是答案了.
狀態轉移方程爲:dp[i][j] += max( dp[i+1][j+1],dp[i+1][j] );好了, 從第n-1行往上面倒退就好了。


6、有一個很長二進制串,求出除以3的餘數是多少,給出算法的時間複雜度。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章