經典面試題(三)附答案 演算法+資料結構+程式碼 微軟Microsoft、谷歌Google、百度、騰訊

yangxi_001發表於2014-06-13
1.判斷單連結串列是否有環,要求空間儘量少(2011年MTK)

如何找出環的連線點在哪裡?

如何知道環的長度?

 

很經典的題目。

1.判斷是否有環。使用兩個指標。一個每次前進1,另一個每次前進2,且都從連結串列第一個元素開始。顯然,如果有環,兩個指標必然會相遇。

2.環的長度。記下第一次的相遇點,這個指標再次從相遇點出發,直到第二次相遇。此時,步長為1的指標所走的步數恰好就是環的長度。

3.環的連結點。記下第一次的相遇點,使一個指標指向這個相遇點,另一個指標指向連結串列第一個元素。然後,兩個指標同步前進,且步長都為1。當兩個指標相遇時所指的點就是環的連線點。

 

連結點這個很不明顯,下面解釋一下。

如圖,設連結串列不在環上的結點有a個,在環上的結點有b個,前兩個指標第一次在第x個結點相遇。

S( i )表示經過的總步長為i之後,所訪問到的結點。

顯然,環的連結點位S( a ),即從起點經過a步之後所到達的結點。

現在要證明:

從第一次的相遇點x再經過a步之後可到達連結點S( a ),即 S( x + a ) = S( a )

由環的週期性可知,只要 a = tb 其中( t = 1, 2, …. ),則S( x + a ) = S( a )

如何證明a = tb?

再看看已知條件,當兩個指標第一次相遇時,必有S( x ) = S( 2x )

由環的週期性可知,必有 2x = x + bt, 即x = tb. 

  1. struct Node  
  2. {  
  3.     int data;  
  4.     Node* next;  
  5.   
  6.     Node( int value ): data(value), next(NULL) {};  
  7. };  
  8.   
  9. //判斷單連結串列是否有環  
  10. bool IsCircle( Node *pHead )  
  11. {  
  12.     //空指標 或 只有一個元素且next為空時,必無環  
  13.     if( pHead == NULL || pHead->next == NULL ) return false;  
  14.       
  15.     Node *pSlow = pHead;  
  16.     Node *pFast = pHead;  
  17.   
  18.     while( ( pFast != NULL ) && ( pFast->next != NULL )  )  
  19.     {  
  20.         //分別按步長1、2前進  
  21.         pSlow = pSlow->next;  
  22.         pFast = pFast->next->next;  
  23.   
  24.         if( pSlow == pFast ) break;  
  25.     }  
  26.     if( ( pFast == NULL ) || ( pFast->next == NULL ) )   
  27.         return false;  
  28.     else   
  29.         return true;  
  30. }  
  31.   
  32. //求環的長度  
  33. int GetLen( Node *pHead )  
  34. {  
  35.     if( pHead == NULL || pHead->next == NULL ) return false;  
  36.       
  37.     Node *pSlow = pHead;  
  38.     Node *pFast = pHead;  
  39.   
  40.     //求相遇點  
  41.     while( ( pFast != NULL ) && ( pFast->next != NULL )  )  
  42.     {  
  43.         pSlow = pSlow->next;  
  44.         pFast = pFast->next->next;  
  45.   
  46.         if( pSlow == pFast ) break;  
  47.     }  
  48.   
  49.     //計算長度  
  50.     int cnt = 0;  
  51.     while( ( pFast != NULL ) && ( pFast->next != NULL )  )  
  52.     {  
  53.         pSlow = pSlow->next;  
  54.         pFast = pFast->next->next;  
  55.         cnt++;  
  56.   
  57.         //再次相遇時,累計的步數就是環的長度  
  58.         if( pSlow == pFast ) break;  
  59.     }  
  60.     return cnt;  
  61. }  
  62. //求環的入口點  
  63. Node* GetEntrance( Node* pHead )  
  64. {  
  65.     if( pHead == NULL || pHead->next == NULL ) return false;  
  66.       
  67.     Node *pSlow = pHead;  
  68.     Node *pFast = pHead;  
  69.   
  70.     //求相遇點  
  71.     while( ( pFast != NULL ) && ( pFast->next != NULL )  )  
  72.     {  
  73.         pSlow = pSlow->next;  
  74.         pFast = pFast->next->next;  
  75.   
  76.         if( pSlow == pFast ) break;  
  77.     }  
  78.   
  79.     pSlow = pHead;  
  80.     while( pSlow != pFast )  
  81.     {  
  82.         //同步前進  
  83.         pSlow = pSlow->next;  
  84.         pFast = pFast->next;  
  85.     }  
  86.     return pSlow;  
  87. }  

 2.用非遞迴的方式合併兩個有序連結串列(2011年MTK)

用遞迴的方式合併兩個有序連結串列

 

基本的連結串列操作,沒什麼好說的。

非遞迴:就是把一個連結串列上的所有結點插入到另一個連結串列中。

遞迴:??

  1. //兩個有序連結串列的合併  
  2. Node* merge( Node* pHeadA, Node* pHeadB )  
  3. {  
  4.     //處理空指標  
  5.     if( pHeadA == NULL || pHeadB == NULL )  
  6.     {  
  7.         return ( pHeadA == NULL ) ? pHeadB : pHeadA;  
  8.     }  
  9.   
  10.     //處理第一個節點  
  11.     Node *px, *py;  
  12.     if( pHeadA->data <= pHeadB->data )  
  13.     {  
  14.         px = pHeadA;    py = pHeadB;  
  15.     }  
  16.     else  
  17.     {  
  18.         px = pHeadB;    py = pHeadA;  
  19.     }  
  20.     Node *pResult = px;  
  21.   
  22.     //將py上的節點按順序插入到px  
  23.     Node *pre = px;  
  24.     px = px->next;  
  25.     while( py != NULL && px != NULL )  
  26.     {  
  27.         //在px上找到py應該插入的位置  
  28.         while( py != NULL && px != NULL && py->data > px->data )  
  29.         {  
  30.             py = py->next;  
  31.             px = px->next;  
  32.             pre = pre->next;   
  33.         }  
  34.         //py插入到pre和px之間  
  35.         if( py != NULL && px != NULL )  
  36.         {  
  37.             //py指標前移  
  38.             Node* tmp = py;  
  39.             py = py->next;  
  40.   
  41.             //pre指標前移  
  42.             Node* tmpPre = pre;  
  43.             pre = pre->next;  
  44.   
  45.             //插入  
  46.             tmp->next = px;  
  47.             tmpPre->next = tmp;  
  48.   
  49.             //px指標前移  
  50.             px = px->next;             
  51.         }  
  52.         else  
  53.             break;  
  54.     }  
  55.     if( px == NULL ) pre->next = py;  
  56.   
  57.     return pResult;  
  58. }  

4程式設計實現:把十進位制數(long型)分別以二進位制和十六進位制形式輸出,不能使用printf系列 

        用位操作實現。十進位制數在計算機裡本來就是按二進位制儲存的,因此通過掩碼和移位操作很容易輸出二進位制形式。這裡,要注意的一點:對最高位符號位的處理。符號位應該單獨處理,否則結果會出錯。十六進位制的處理和二進位制基本相同,只是每次處理四位。 

  1. void LongFormat( long value )  
  2. {     
  3.     //處理符號位  
  4.     long mask = 0x1 << ( 8 * sizeof(long) - 1 );  
  5.     if( value & mask ) cout << "1";  
  6.     else cout << "0";  
  7.     //轉換為二進位制  
  8.     mask = 0x1 << ( 8 * sizeof(long) - 2 );  
  9.     forint i=1; i<8*sizeof(long); i++ )  
  10.     {  
  11.         if( value & mask ) cout << "1";  
  12.         else cout << "0";  
  13.         mask >>= 1;  
  14.     }  
  15.     cout << endl;  
  16.   
  17.     //處理符號位  
  18.     cout << "0x";   
  19.     mask = 0xF << ( 8 * sizeof(long) - 4 );  
  20.     long tmp = ( value & mask ) >> ( 8 * sizeof(long) - 4 );  
  21.     if( tmp < 10 )  
  22.         cout << tmp;  
  23.     else  
  24.         cout << (char)( 'a' + ( tmp - 10 ) );  
  25.     //轉換為十六進位制  
  26.     mask = 0xF << ( 8 * sizeof(long) - 8 );  
  27.     forint i=1; i<2*sizeof(long); i++ )  
  28.     {  
  29.         tmp = ( value & mask ) >> ( 8 * sizeof(long) - 4 * i - 4 );  
  30.         if( tmp < 10 )  
  31.             cout << tmp;  
  32.         else  
  33.             cout << (char)( 'a' + ( tmp - 10 ) );  
  34.   
  35.         mask >>= 4;   
  36.     }  
  37. }  

5.程式設計實現:找出兩個字串中最大公共子字串,如"abccade","dgcadde"的最大子串為"cad" 

有人說:可用KMP。可惜KMP忘了,找時間補一下。

還有人說:用兩個字串,一個作行、一個作列,形成一個矩陣。相同的位置填1,不同的位置填0。然後找哪個斜線方向上1最多,就可以得到最大公共子字串。空間複製度0( m*n ),感覺時間上也差不多O( m*n )

沒想到什麼好辦法,只會用最笨的辦法O( m*n )。即,對於字串A中的每個字元,在字串B中找以它為首的最大子串。哎,即便是這個最笨的方法,也寫了好長時間,汗。 

  1. void GetSubStr( char *strA, char *strB, char *ans )  
  2. {     
  3.     int max = 0;  
  4.     char *pAns = NULL;  
  5.   
  6.     //遍歷字串A  
  7.     forint i=0; *(strA+i) != '\0'; i++ )  
  8.     {  
  9.         //儲存strB的首地址,每次都從strB的第一個元素開始比較  
  10.         char *pb = strB;  
  11.         while( *pb != '\0' )  
  12.         {  
  13.             //儲存strA的首地址  
  14.             char *pa = strA + i;  
  15.             int cnt = 0;  
  16.             char *pBegin = pb;  
  17.   
  18.             //如果找到一個相等的元素  
  19.             if( *pb == *pa )  
  20.             {  
  21.                 while( *pb == *pa && *pb != '\0' )   
  22.                 {  
  23.                     pa++;  
  24.                     pb++;  
  25.                     cnt++;  
  26.                 }  
  27.                 if( cnt > max )  
  28.                 {  
  29.                     max = cnt;  
  30.                     pAns = pBegin;  
  31.                 }     
  32.                 if( *pb == '\0' ) break;  
  33.             }  
  34.             else  
  35.                 pb++;             
  36.         }         
  37.     }  
  38.     //返回結果  
  39.     memcpy( ans, pAns, max );     
  40.     *(ans+max) = '\0';  
  41. }  

6.有雙向迴圈連結串列結點定義為:

struct node

{

  int data;

  struct node *front,*next;

};

有兩個雙向迴圈連結串列A,B,知道其頭指標為:pHeadA,pHeadB,請寫一函式將兩連結串列中data值相同的結點刪除。

 

       沒什麼NB演算法。就是遍歷對連結串列A,對A的每個元素,看它是否在連結串列B中出現。如果在B中出現,則把所有的出現全部刪除,同時也在A中刪除這個元素。

思路很簡單,實現起來也挺麻煩。畢竟,雙向迴圈連結串列也算是線性資料結構中最複雜的了。如何判斷雙向迴圈連結串列的最後一個元素?p->next == pHead.

刪除操作:

雙向迴圈連結串列只有一個節點時

雙向迴圈連結串列至少有兩個節點時

  1. struct Node  
  2. {   
  3.   int data;  
  4.   struct Node *front,*next;  
  5.   Node( int value ): data( value ), front( NULL ), next( NULL ) { };  
  6.   void SetPointer( Node *pPre, Node *pNext ) { front = pPre; next = pNext; };  
  7. };  
  8.   
  9. //如果成功刪除返回真。否則,返回假。  
  10. bool DeleteValue( Node *&pHead, int target )  
  11. {  
  12.     if( pHead == NULL ) return false;     
  13.   
  14.     //至少有兩個元素  
  15.     bool flag = false;  
  16.     Node* ph = pHead;  
  17.     while( ph->next != pHead  )  
  18.     {  
  19.         Node *pPre = ph->front;  
  20.         Node *pNext = ph->next;  
  21.   
  22.         if( ph->data == target )  
  23.         {  
  24.             //如果刪除的是第一個元素  
  25.             if( ph == pHead ) pHead = ph->next;            
  26.   
  27.             pPre->next = pNext;  
  28.             pNext->front = pPre;   
  29.   
  30.             Node *tmp = ph;  
  31.             delete tmp;  
  32.               
  33.             //設定刪除標記  
  34.             flag = true;  
  35.         }  
  36.         ph = pNext;  
  37.     }  
  38.     //只有一個元素或最後一個元素  
  39.     if( ph->next == pHead )  
  40.     {  
  41.         if( ph->data == target )  
  42.         {  
  43.             //如果要刪除的是最後一個元素  
  44.             if( ph->front != ph )  
  45.             {                 
  46.                 Node *pPre = ph->front;  
  47.                 Node *pNext = ph->next;  
  48.                 pPre->next = pNext;  
  49.                 pNext->front = pPre;   
  50.   
  51.                 Node *tmp = ph;  
  52.                 delete tmp;  
  53.             }  
  54.             else  
  55.             {  
  56.                 delete pHead;  
  57.                 pHead = NULL;             
  58.             }  
  59.             flag = true;              
  60.         }         
  61.     }  
  62.     return flag;  
  63. }  
  64.   
  65.   
  66. void DeleteSame( Node *&pHeadA, Node *&pHeadB )  
  67. {  
  68.     if( pHeadA != NULL && pHeadB != NULL )  
  69.     {  
  70.         Node *pa = pHeadA;  
  71.         while( pa->next != pHeadA )  
  72.         {             
  73.             //如果B中含有pa->data,並且已經刪除            
  74.             if( DeleteValue( pHeadB, pa->data ) )  
  75.             {  
  76.                 //在A中刪除pa->data  
  77.                 Node *tmp = pa->next;  
  78.                 DeleteValue( pHeadA, pa->data );  
  79.                 pa = tmp;  
  80.             }  
  81.             else  
  82.                 pa = pa->next;  
  83.         }  
  84.         //只有一個元素或最後一個元素               
  85.         if( DeleteValue( pHeadB, pa->data ) )  
  86.         {  
  87.             DeleteValue( pHeadA, pa->data );  
  88.         }         
  89.     }  
  90. }  

7.設計函式int atoi(char *s)。

int i=(j=4,k=8,l=16,m=32); printf(“%d”, i); 輸出是多少?

解釋區域性變數、全域性變數和靜態變數的含義。

解釋堆和棧的區別。

論述含引數的巨集與函式的優缺點。

 

1.字串轉整形,嘿嘿,前面已寫過了。

2.逗號表示式的值等於最後一個逗號之後的表示式的值。對應本題,即i=(m=32)

3.區域性變數:在函式內定義的變數。作用域範圍:只在定義它的塊內有效。

全域性變數:在函式之外定義的變數。作用域範圍:從定義的地方開始直到檔案末尾都有效。

靜態變數:static變數,屬於靜態儲存方式。靜態區域性變數在函式內定義,生存期是整個原始碼。但是,作用域範圍只在定義它的函式內有效。靜態全域性變數與一般的全域性變數:一般全域性變數在整個源程式內有效,靜態全域性變數只在所在檔案內有效。

4.堆:一般new出來的變數都在堆裡,這裡變數要由程式設計師自己管理,即在不用的時候要及時釋放,防止記憶體洩露。

棧:一般區域性變數、函式的引數都在棧裡,他們是由編譯器來自動管理的。 

8.順時針列印矩陣

題目:輸入一個矩陣,按照從外向裡以順時針的順序依次列印出每一個數字。

例如:如果輸入如下矩陣:

1              2              3              4

5              6              7              8

9              10             11             12

13             14             15             16

則依次列印出數字, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10。

分析:包括Autodesk、EMC在內的多家公司在面試或者筆試裡採用過這道題

 

        本來想寫遞迴的,結果遞迴的終止條件比較複雜。因為每次把最外面一圈都出來了,所以矩形的行列都減小2,而且還要記錄當前矩形的起始位置。遞迴終止條件,要考慮行列為0、1的情況。哎,想不清楚。最後還是非遞迴的好寫。也很簡單,沒啥所的,直接看程式碼把。

  1. const int MAX_ROW = 100;  
  2. const int MAX_COL = 100;  
  3.   
  4. void PrintMatrix( int data[][MAX_COL], int row, int col )  
  5. {  
  6.     int top = 0;  
  7.     int bottom = row-1;  
  8.     int left = 0;  
  9.     int right = col-1;  
  10.   
  11.     int cnt = 0;  
  12.     int total = row * col;  
  13.     while( cnt < total )  
  14.     {  
  15.         //從左到右,列印最上面一行  
  16.         int j;  
  17.         for( j=left; j<=right && cnt<total; j++ )  
  18.         {  
  19.             cout << data[top][j] <<" ";  
  20.             cnt++;  
  21.         }  
  22.         top++;  
  23.   
  24.         //從上到下,列印最右面一列  
  25.         for( j=top; j<=bottom && cnt<total; j++ )  
  26.         {  
  27.             cout << data[j][right] << " ";  
  28.             cnt++;  
  29.         }  
  30.         right--;  
  31.   
  32.         //從右到左,列印最下面一行  
  33.         for( j=right; j>=left && cnt<total; j-- )  
  34.         {  
  35.             cout << data[bottom][j] << " ";  
  36.             cnt++;  
  37.         }  
  38.         bottom--;  
  39.   
  40.         //從下到上,列印最左邊一列  
  41.         for( j=bottom; j>=top && cnt<total; j-- )  
  42.         {  
  43.             cout << data[j][left] << " ";  
  44.             cnt++;  
  45.         }  
  46.         left++;  
  47.     }             
  48. }  

9.對稱子字串的最大長度

題目:輸入一個字串,輸出該字串中對稱的子字串的最大長度。

比如輸入字串“google”,由於該字串裡最長的對稱子字串是“goog”,因此輸出。

分析:可能很多人都寫過判斷一個字串是不是對稱的函式,這個題目可以看成是該函式的加強版

 

10.用1、2、3、4、5、6這六個數字,寫一個main函式,列印出所有不同的排列,如:512234、412345等,要求:"4"不能在第三位,"3"與"5"不能相連.

 

       先不考慮限制條件,我們可以用遞迴列印出所有的排列(嘿嘿,這個前面寫過,可以用遞迴處理)。然後,只要在遞迴終止時,把限制條件加上,這樣只把滿足條件的排列列印出來,就可以了。

  1. bool IsValid( char *str )  
  2. {  
  3.     forint i=1; *(str+i) != '\0'; i++ )  
  4.     {  
  5.         if( i == 2 && *(str+i) == '4' ) return false;  
  6.   
  7.         if( *(str+i) == '3' && *(str+i-1) == '5' || *(str+i) == '5' && *(str+i-1) == '3' )  
  8.             return false;  
  9.     }  
  10.     return true;  
  11. }  
  12.   
  13. void PrintStr( char *str, char *start )  
  14. {  
  15.     if( str == NULL ) return;  
  16.       
  17.     if( *start == '\0' )  
  18.     {  
  19.         if( IsValid( str ) ) cout << str << endl;  
  20.     }  
  21.   
  22.     forchar *ptmp = start; *ptmp != '\0'; ptmp++ )  
  23.     {  
  24.         char tmp = *start;  
  25.         *start = *ptmp;  
  26.         *ptmp = tmp;  
  27.   
  28.         PrintStr( str, start+1 );  
  29.   
  30.         tmp = *start;  
  31.         *start = *ptmp;  
  32.         *ptmp = tmp;   
  33.     }  
  34. }  

11。微軟面試題

一個有序數列,序列中的每一個值都能夠被2或者3或者5所整除,1是這個序列的第一個元素。求第1500個值是多少?

 

       2、3、5的最小公倍數是30。[ 1, 30]內符合條件的數有22個。如果能看出[ 31, 60]內也有22個符合條件的數,那問題就容易解決了。也就是說,這些數具有周期性,且週期為30.

       第1500個數是:1500/22=68   1500%68=4。也就是說:第1500個數相當於經過了68個週期,然後再取下一個週期內的第4個數。一個週期內的前4個數:2,3,4,5。

故,結果為68*30=2040+5=2045

 

12.從尾到頭輸出連結串列

題目:輸入一個連結串列的頭結點,從尾到頭反過來輸出每個結點的值。連結串列結點定義如下:

struct ListNode

{

  int  m_nKey;

  ListNode* m_pNext;

};

分析:這是一道很有意思的面試題。該題以及它的變體經常出現在各大公司的面試、筆試題中。

 

連結串列的反向輸出。前面我們討論過:連結串列的逆序,使用3個額外指標,遍歷一遍連結串列即可完成。這裡當然可以先把連結串列逆序,然後再輸出。連結串列上使用遞迴一般也很簡單,雖然遞迴要壓棧,但程式看起來很簡潔。

  1. struct ListNode  
  2. {  
  3.     int  m_nKey;  
  4.     ListNode* m_pNext;  
  5. };  
  6.   
  7. void PrintReverse( ListNode* pHead )  
  8. {  
  9.     ListNode* ph = pHead;  
  10.     if( ph != NULL )  
  11.     {  
  12.         PrintReverse( ph->m_pNext );  
  13.         cout << ph->m_nKey << " ";  
  14.     }  
  15. }  
 

相關文章