經典面試題(三)附答案 演算法+資料結構+程式碼 微軟Microsoft、谷歌Google、百度、騰訊
如何找出環的連線點在哪裡?
如何知道環的長度?
很經典的題目。
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.
- struct Node
- {
- int data;
- Node* next;
- Node( int value ): data(value), next(NULL) {};
- };
- //判斷單連結串列是否有環
- bool IsCircle( Node *pHead )
- {
- //空指標 或 只有一個元素且next為空時,必無環
- if( pHead == NULL || pHead->next == NULL ) return false;
- Node *pSlow = pHead;
- Node *pFast = pHead;
- while( ( pFast != NULL ) && ( pFast->next != NULL ) )
- {
- //分別按步長1、2前進
- pSlow = pSlow->next;
- pFast = pFast->next->next;
- if( pSlow == pFast ) break;
- }
- if( ( pFast == NULL ) || ( pFast->next == NULL ) )
- return false;
- else
- return true;
- }
- //求環的長度
- int GetLen( Node *pHead )
- {
- if( pHead == NULL || pHead->next == NULL ) return false;
- Node *pSlow = pHead;
- Node *pFast = pHead;
- //求相遇點
- while( ( pFast != NULL ) && ( pFast->next != NULL ) )
- {
- pSlow = pSlow->next;
- pFast = pFast->next->next;
- if( pSlow == pFast ) break;
- }
- //計算長度
- int cnt = 0;
- while( ( pFast != NULL ) && ( pFast->next != NULL ) )
- {
- pSlow = pSlow->next;
- pFast = pFast->next->next;
- cnt++;
- //再次相遇時,累計的步數就是環的長度
- if( pSlow == pFast ) break;
- }
- return cnt;
- }
- //求環的入口點
- Node* GetEntrance( Node* pHead )
- {
- if( pHead == NULL || pHead->next == NULL ) return false;
- Node *pSlow = pHead;
- Node *pFast = pHead;
- //求相遇點
- while( ( pFast != NULL ) && ( pFast->next != NULL ) )
- {
- pSlow = pSlow->next;
- pFast = pFast->next->next;
- if( pSlow == pFast ) break;
- }
- pSlow = pHead;
- while( pSlow != pFast )
- {
- //同步前進
- pSlow = pSlow->next;
- pFast = pFast->next;
- }
- return pSlow;
- }
2.用非遞迴的方式合併兩個有序連結串列(2011年MTK)
用遞迴的方式合併兩個有序連結串列
基本的連結串列操作,沒什麼好說的。
非遞迴:就是把一個連結串列上的所有結點插入到另一個連結串列中。
遞迴:??
- //兩個有序連結串列的合併
- Node* merge( Node* pHeadA, Node* pHeadB )
- {
- //處理空指標
- if( pHeadA == NULL || pHeadB == NULL )
- {
- return ( pHeadA == NULL ) ? pHeadB : pHeadA;
- }
- //處理第一個節點
- Node *px, *py;
- if( pHeadA->data <= pHeadB->data )
- {
- px = pHeadA; py = pHeadB;
- }
- else
- {
- px = pHeadB; py = pHeadA;
- }
- Node *pResult = px;
- //將py上的節點按順序插入到px
- Node *pre = px;
- px = px->next;
- while( py != NULL && px != NULL )
- {
- //在px上找到py應該插入的位置
- while( py != NULL && px != NULL && py->data > px->data )
- {
- py = py->next;
- px = px->next;
- pre = pre->next;
- }
- //py插入到pre和px之間
- if( py != NULL && px != NULL )
- {
- //py指標前移
- Node* tmp = py;
- py = py->next;
- //pre指標前移
- Node* tmpPre = pre;
- pre = pre->next;
- //插入
- tmp->next = px;
- tmpPre->next = tmp;
- //px指標前移
- px = px->next;
- }
- else
- break;
- }
- if( px == NULL ) pre->next = py;
- return pResult;
- }
4程式設計實現:把十進位制數(long型)分別以二進位制和十六進位制形式輸出,不能使用printf系列
用位操作實現。十進位制數在計算機裡本來就是按二進位制儲存的,因此通過掩碼和移位操作很容易輸出二進位制形式。這裡,要注意的一點:對最高位符號位的處理。符號位應該單獨處理,否則結果會出錯。十六進位制的處理和二進位制基本相同,只是每次處理四位。
- void LongFormat( long value )
- {
- //處理符號位
- long mask = 0x1 << ( 8 * sizeof(long) - 1 );
- if( value & mask ) cout << "1";
- else cout << "0";
- //轉換為二進位制
- mask = 0x1 << ( 8 * sizeof(long) - 2 );
- for( int i=1; i<8*sizeof(long); i++ )
- {
- if( value & mask ) cout << "1";
- else cout << "0";
- mask >>= 1;
- }
- cout << endl;
- //處理符號位
- cout << "0x";
- mask = 0xF << ( 8 * sizeof(long) - 4 );
- long tmp = ( value & mask ) >> ( 8 * sizeof(long) - 4 );
- if( tmp < 10 )
- cout << tmp;
- else
- cout << (char)( 'a' + ( tmp - 10 ) );
- //轉換為十六進位制
- mask = 0xF << ( 8 * sizeof(long) - 8 );
- for( int i=1; i<2*sizeof(long); i++ )
- {
- tmp = ( value & mask ) >> ( 8 * sizeof(long) - 4 * i - 4 );
- if( tmp < 10 )
- cout << tmp;
- else
- cout << (char)( 'a' + ( tmp - 10 ) );
- mask >>= 4;
- }
- }
5.程式設計實現:找出兩個字串中最大公共子字串,如"abccade","dgcadde"的最大子串為"cad"
有人說:可用KMP。可惜KMP忘了,找時間補一下。
還有人說:用兩個字串,一個作行、一個作列,形成一個矩陣。相同的位置填1,不同的位置填0。然後找哪個斜線方向上1最多,就可以得到最大公共子字串。空間複製度0( m*n ),感覺時間上也差不多O( m*n )
沒想到什麼好辦法,只會用最笨的辦法O( m*n )。即,對於字串A中的每個字元,在字串B中找以它為首的最大子串。哎,即便是這個最笨的方法,也寫了好長時間,汗。
- void GetSubStr( char *strA, char *strB, char *ans )
- {
- int max = 0;
- char *pAns = NULL;
- //遍歷字串A
- for( int i=0; *(strA+i) != '\0'; i++ )
- {
- //儲存strB的首地址,每次都從strB的第一個元素開始比較
- char *pb = strB;
- while( *pb != '\0' )
- {
- //儲存strA的首地址
- char *pa = strA + i;
- int cnt = 0;
- char *pBegin = pb;
- //如果找到一個相等的元素
- if( *pb == *pa )
- {
- while( *pb == *pa && *pb != '\0' )
- {
- pa++;
- pb++;
- cnt++;
- }
- if( cnt > max )
- {
- max = cnt;
- pAns = pBegin;
- }
- if( *pb == '\0' ) break;
- }
- else
- pb++;
- }
- }
- //返回結果
- memcpy( ans, pAns, max );
- *(ans+max) = '\0';
- }
6.有雙向迴圈連結串列結點定義為:
struct node
{
int data;
struct node *front,*next;
};
有兩個雙向迴圈連結串列A,B,知道其頭指標為:pHeadA,pHeadB,請寫一函式將兩連結串列中data值相同的結點刪除。
沒什麼NB演算法。就是遍歷對連結串列A,對A的每個元素,看它是否在連結串列B中出現。如果在B中出現,則把所有的出現全部刪除,同時也在A中刪除這個元素。
思路很簡單,實現起來也挺麻煩。畢竟,雙向迴圈連結串列也算是線性資料結構中最複雜的了。如何判斷雙向迴圈連結串列的最後一個元素?p->next == pHead.
刪除操作:
雙向迴圈連結串列只有一個節點時
雙向迴圈連結串列至少有兩個節點時
- struct Node
- {
- int data;
- struct Node *front,*next;
- Node( int value ): data( value ), front( NULL ), next( NULL ) { };
- void SetPointer( Node *pPre, Node *pNext ) { front = pPre; next = pNext; };
- };
- //如果成功刪除返回真。否則,返回假。
- bool DeleteValue( Node *&pHead, int target )
- {
- if( pHead == NULL ) return false;
- //至少有兩個元素
- bool flag = false;
- Node* ph = pHead;
- while( ph->next != pHead )
- {
- Node *pPre = ph->front;
- Node *pNext = ph->next;
- if( ph->data == target )
- {
- //如果刪除的是第一個元素
- if( ph == pHead ) pHead = ph->next;
- pPre->next = pNext;
- pNext->front = pPre;
- Node *tmp = ph;
- delete tmp;
- //設定刪除標記
- flag = true;
- }
- ph = pNext;
- }
- //只有一個元素或最後一個元素
- if( ph->next == pHead )
- {
- if( ph->data == target )
- {
- //如果要刪除的是最後一個元素
- if( ph->front != ph )
- {
- Node *pPre = ph->front;
- Node *pNext = ph->next;
- pPre->next = pNext;
- pNext->front = pPre;
- Node *tmp = ph;
- delete tmp;
- }
- else
- {
- delete pHead;
- pHead = NULL;
- }
- flag = true;
- }
- }
- return flag;
- }
- void DeleteSame( Node *&pHeadA, Node *&pHeadB )
- {
- if( pHeadA != NULL && pHeadB != NULL )
- {
- Node *pa = pHeadA;
- while( pa->next != pHeadA )
- {
- //如果B中含有pa->data,並且已經刪除
- if( DeleteValue( pHeadB, pa->data ) )
- {
- //在A中刪除pa->data
- Node *tmp = pa->next;
- DeleteValue( pHeadA, pa->data );
- pa = tmp;
- }
- else
- pa = pa->next;
- }
- //只有一個元素或最後一個元素
- if( DeleteValue( pHeadB, pa->data ) )
- {
- DeleteValue( pHeadA, pa->data );
- }
- }
- }
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的情況。哎,想不清楚。最後還是非遞迴的好寫。也很簡單,沒啥所的,直接看程式碼把。
- const int MAX_ROW = 100;
- const int MAX_COL = 100;
- void PrintMatrix( int data[][MAX_COL], int row, int col )
- {
- int top = 0;
- int bottom = row-1;
- int left = 0;
- int right = col-1;
- int cnt = 0;
- int total = row * col;
- while( cnt < total )
- {
- //從左到右,列印最上面一行
- int j;
- for( j=left; j<=right && cnt<total; j++ )
- {
- cout << data[top][j] <<" ";
- cnt++;
- }
- top++;
- //從上到下,列印最右面一列
- for( j=top; j<=bottom && cnt<total; j++ )
- {
- cout << data[j][right] << " ";
- cnt++;
- }
- right--;
- //從右到左,列印最下面一行
- for( j=right; j>=left && cnt<total; j-- )
- {
- cout << data[bottom][j] << " ";
- cnt++;
- }
- bottom--;
- //從下到上,列印最左邊一列
- for( j=bottom; j>=top && cnt<total; j-- )
- {
- cout << data[j][left] << " ";
- cnt++;
- }
- left++;
- }
- }
9.對稱子字串的最大長度
題目:輸入一個字串,輸出該字串中對稱的子字串的最大長度。
比如輸入字串“google”,由於該字串裡最長的對稱子字串是“goog”,因此輸出。
分析:可能很多人都寫過判斷一個字串是不是對稱的函式,這個題目可以看成是該函式的加強版
10.用1、2、3、4、5、6這六個數字,寫一個main函式,列印出所有不同的排列,如:512234、412345等,要求:"4"不能在第三位,"3"與"5"不能相連.
先不考慮限制條件,我們可以用遞迴列印出所有的排列(嘿嘿,這個前面寫過,可以用遞迴處理)。然後,只要在遞迴終止時,把限制條件加上,這樣只把滿足條件的排列列印出來,就可以了。
- bool IsValid( char *str )
- {
- for( int i=1; *(str+i) != '\0'; i++ )
- {
- if( i == 2 && *(str+i) == '4' ) return false;
- if( *(str+i) == '3' && *(str+i-1) == '5' || *(str+i) == '5' && *(str+i-1) == '3' )
- return false;
- }
- return true;
- }
- void PrintStr( char *str, char *start )
- {
- if( str == NULL ) return;
- if( *start == '\0' )
- {
- if( IsValid( str ) ) cout << str << endl;
- }
- for( char *ptmp = start; *ptmp != '\0'; ptmp++ )
- {
- char tmp = *start;
- *start = *ptmp;
- *ptmp = tmp;
- PrintStr( str, start+1 );
- tmp = *start;
- *start = *ptmp;
- *ptmp = tmp;
- }
- }
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個額外指標,遍歷一遍連結串列即可完成。這裡當然可以先把連結串列逆序,然後再輸出。連結串列上使用遞迴一般也很簡單,雖然遞迴要壓棧,但程式看起來很簡潔。
- struct ListNode
- {
- int m_nKey;
- ListNode* m_pNext;
- };
- void PrintReverse( ListNode* pHead )
- {
- ListNode* ph = pHead;
- if( ph != NULL )
- {
- PrintReverse( ph->m_pNext );
- cout << ph->m_nKey << " ";
- }
- }
相關文章
- 經典面試題(一)附答案 演算法+資料結構+程式碼 微軟Microsoft、谷歌Google、百度、騰訊面試題演算法資料結構微軟ROS谷歌Go
- 經典面試題(二)附答案 演算法+資料結構+程式碼 微軟Microsoft、谷歌Google、百度、騰訊面試題演算法資料結構微軟ROS谷歌Go
- 經典面試題(四)附答案 演算法+資料結構+程式碼 微軟Microsoft、谷歌Google、百度、騰訊面試題演算法資料結構微軟ROS谷歌Go
- Runtime經典面試題(附答案)面試題
- Python經典面試題(附答案)!Python面試題
- 程式碼面試需要知道的8種資料結構(附面試題及答案連結)資料結構面試題
- 微軟等資料結構+演算法面試100題全部答案集錦微軟資料結構演算法面試
- 2萬字60道MySQL經典面試題總結(附答案)MySql面試題
- 2萬字70道Java經典面試題總結(附答案)Java面試題
- google經典演算法面試題-雞蛋問題Go演算法面試題
- 面試不會演算法和資料結構,經典面試題講解來了!演算法資料結構面試題
- 前端經典面試題(有答案)前端面試題
- SQL經典面試題及答案SQL面試題
- Google經典面試題解析Go面試題
- 49個Spring經典面試題總結,附帶答案,趕緊收藏Spring面試題
- PHP經典面試題,有答案哦PHP面試題
- 經典的八個PHP高階工程面試題(附答案)PHP面試題
- jQuery經典面試題及答案精選jQuery面試題
- Web前端經典面試試題及答案(參考連結)Web前端面試
- Python入門教程之Python經典面試題(附答案)Python面試題
- 經典資料結構和演算法回顧資料結構演算法
- 【演算法與資料結構】經典排序演算法總結演算法資料結構排序
- sql 經典面試題及答案(選課表)SQL面試題
- 經典Java面試題彙總及答案解析Java面試題
- 那些web前端經典面試題大全及答案Web前端面試題
- 69 個經典 Spring 面試題和答案Spring面試題
- 69個經典Spring面試題和答案Spring面試題
- 資料探勘面試筆試題(附答案)面試筆試
- 大資料某公司面試題-附答案大資料面試題
- 資料結構::一些經典的大資料題資料結構大資料
- 經典演算法面試題(二)演算法面試題
- 微軟經典面試100題系列(部分)微軟面試
- 100+經典Java面試題及答案解析Java面試題
- 騰訊招聘Python程式設計師面試題目:Python資料結構與演算法Python程式設計師面試題資料結構演算法
- 微軟人工智慧和資料科學25個經典面試問題!微軟人工智慧資料科學面試
- 演算法、資料結構 常見面試題演算法資料結構面試題
- 經典面試問題:12小球問題演算法(原始碼)面試演算法原始碼
- c/c++經典面試試題及標準答案C++面試