經典面試題(一)附答案 演算法+資料結構+程式碼 微軟Microsoft、谷歌Google、百度、騰訊
1. 有一個整數陣列,請求出兩兩之差絕對值最小的值。記住,只要得出最小值即可,不需要求出是哪兩個數。(Microsoft)
方法1:兩兩作差求絕對值,並取最小,O( n2 )。
方法2:排序,相鄰兩點作差求絕對值,並取最小,O( nlgn ).
方法3:有沒有O( n )的解法?網上有如下解法:
設陣列A = { a1, a2, … , an }, 求 s = min( |ai - aj| ), 其中1<= i, j <=n.
設B = { b1, b2, … , bn-1 }, 且 bi = ai – ai+1
即:b1 = a1 – a2, b2 = a2 – a3, b3 = a3 – a4, …
於是有如下規律:
例如:a3 – a5 = ( a3 – a4 ) + ( a4 – a5 ) =b3 + b4
a1 – a6 = b1 + b2 + … + b5
即:ai – aj = bi + … + bj-1
則陣列A中任意兩個數的差,都可以用陣列B中一個欄位的和表示。
則原問題可以轉換為:
在陣列B中,求連續的某一段,使其和的絕對值最小。(只求最小值,不需要知道具體是哪些數)
例如 B = { 1, -2, 3, -1, -9, 7, -5, 6 };
則絕對值最小值為0,具體是{ -2, 3, -1 } 或 {3, -1, -9, 7}
網上的解法,一般到這裡就沒下文了。只是簡單的提了一下,類似於最大子序列的和。具體怎麼做,還要自己想想。
最大子序列和利用DP,可O( n )求解。這題咋做?糾結。
2. 寫一個函式,檢查字元是否是整數,如果是,返回其整數值。(或者:怎樣只用4行程式碼編寫出一個從字串到長整形的函式?)
據說此題是,Microsoft的大牛隻有了4行程式碼就給出了答案。
可惜,不知道是怎麼寫的。自己試著寫寫,當然可能會不至4行。單純追求行數,也沒什麼意義,如果你願意可以把所有的程式都寫成一行。
注意:
1. 處理前導空格
2. 處理正負號
3. 處理進位制(16進位制、8進位制、10進位制)
4. 非法字元( 0---9, a---f, A---F)
5. 注意整數的範圍,不能溢位
- bool StrToInt( char *pc, long &value )
- {
- //去掉前導空格
- while( ( *pc==' ' || *pc=='\t' ) && *pc != '\0' ) pc++;
- if( *pc == '\0' ) return false;
- //處理正負號
- int sign = 1;
- if( *pc == '+' || *pc == '-' )
- {
- if( *(pc+1) =='\0' ) return false;
- if( *pc == '-' ) sign = -1;
- pc++;
- }
- //處理數值
- long tmp = 0;
- while( *pc != '\0' )
- {
- tmp *= 10;
- //++優先順序比*高
- if( *pc < '0' && *pc > '9' ) return false;
- tmp += ( *pc++ - '0' );
- }
- value = tmp * sign;
- return true;
- }
3. 給出一個函式來輸出一個字串的所有排列
方法1:
一個簡單的DFS。從後往前不斷互動。N個字母求全排列,O( n! )。具體實現,看程式碼吧。
方法2:
如果不會寫遞迴,也可以利用STL。STL裡有一個next_permutation函式。利用這個函式可以返回大於原字串的下一個字典序列。當字串為最大字典序列時,函式返回false。這樣只要先對原字串排序,然後不斷呼叫next_permuation即可。
- inline void Exchange( char *px, char *py )
- {
- char tmp = *px;
- *px = *py;
- *py = tmp;
- }
- void PrintStrPermut( char *pstr, char *pbegin )
- {
- //處理空字串
- if( pstr == NULL || pbegin == NULL ) return;
- //遞迴終止條件
- if( *pbegin == '\0' )
- cout << pstr << endl;
- else
- {
- for( char *p=pbegin; *p!='\0'; p++ )
- {
- Exchange( p, pbegin );
- PrintStrPermut( pstr, pbegin+1 );
- Exchange( p, pbegin );
- }
- }
- }
- void PrintStrPermut2( char *pstr )
- {
- char *p = pstr;
- while( *p != '\0' ) p++;
- sort( pstr, p );
- cout << pstr << endl;
- while( next_permutation( pstr, p ) )
- {
- cout << pstr << endl;
- }
- }
4.請編寫實現malloc()記憶體分配函式功能一樣的程式碼
這題比較難,要是不懂點OS的記憶體管理,根本就無從下手。
我們知道呼叫malloc()後,OS就要想方設法為我們返回一塊空閒空間。這就涉及到OS的記憶體管理。OS的記憶體管理可以這樣考慮:
假設整塊記憶體有128K
初始狀態,128K都是空閒
第一次請求,申請了16k,空閒112K
第二次請求,申請了32K,空閒80K
第三次請求,申請了8K,空閒72K
第二次請求申請的32K被釋放,空閒108K
第四次請求,申請了24K,空閒84K
…
從上面的例子可以看出,一整塊連續的空閒記憶體塊,經過一段時間的使用,會被無情的劃分為許多小塊。這些小塊大小不等,並且有的空閒、有的被佔用。
當呼叫malloc時,OS就沿記憶體掃描,找到一塊夠大的空閒塊,從中劃分出要使用的部分,將這部分標記為己分配,並返回這部分的首地址。如果,空閒的塊都是些小的碎片,那就悲具了(當然,OS可以把將相鄰的空閒塊合併,再嘗試)。
現在,模擬一下malloc的過程:
為了便於管理,首先定義記憶體控制塊mcb。這個mcb記錄兩個資訊:塊是否空閒、塊的大小。即,每個分配出去的塊,其實都帶有一個mcb,只不過這個mcb位於塊的最前端,返回該使用者的指標剛好指向mcb之後,所以對使用者是不可見的。
現在,就可以處理free了。Free只要把已分配的記憶體塊重新標記為空閒即可,這裡當然要用到該快的mcb了。
Malloc簡單來說,就是維護幾個指標,根據分配請求修改指標位置。對於要分配的塊,將標記置位己分配,並返回這部分的首地址。
參考http://lklkdawei.blog.163.com/blog/static/32574109200881445518891/,這裡講的很清楚,還附有程式碼,我就不狗尾續貂了。
5. 字串A的後幾個位元組和字串B的前幾個位元組重疊。
這題似乎沒什麼玄機,就是個簡單的字串處理。使用strlen和memcpy可以完成,見程式碼。
- bool StrOverlap( char *strA, char *strB, int cnt, char *strC )
- {
- int sizeA = (int)strlen( strA );
- int sizeB = (int)strlen( strB );
- if( cnt > sizeA || cnt > sizeB ) return false;
- memcpy( strC, strA, sizeA-cnt );
- memcpy( strC+sizeA-cnt, strB+cnt, sizeB-cnt );
- //注意新增結束標記
- strC[sizeA+sizeB-2*cnt] = '\0';
- return true;
- }
6. 怎樣編寫一個程式,把一個有序整數陣列放到二叉樹中?
由陣列建立排序二叉樹。因為陣列已排序,所以可以進行類似排序二叉樹上的查詢。感覺有點類似先序遍歷,每次先處理根節點,然後分別是左子樹、右子樹。具體做法是:
1.整個陣列對應一個二叉樹,則中間元素對應二叉樹的根節點
2.中間元素左邊的部分對應左子樹、右邊的部分對應右子樹
3.對左右兩部分再繼續遞迴呼叫。
- struct BiTreeNode
- {
- int data;
- BiTreeNode* leftChild;
- BiTreeNode* rightChild;
- //建構函式,初始化成員變數
- BiTreeNode(): data(0), leftChild(0), rightChild(0){};
- };
- void ArrayToTree( int *pi, int left, int right, BiTreeNode *&root )
- {
- if( left <= right )
- {
- int mid = ( left + right ) / 2;
- root = new BiTreeNode;
- root->data = pi[mid];
- ArrayToTree( pi, left, mid-1, root->leftChild );
- ArrayToTree( pi, mid+1, right, root->rightChild );
- }
- }
7. 怎樣從頂部開始逐層列印二叉樹結點資料?請程式設計。
用佇列容易實現。網上有人說有非佇列的實現,不過還是用指標把每一層的點都連了起來,然後逐層列印。這種方法和用佇列把每層的節點存起來大同小異。- void PrintTreeByLevel( BiTreeNode *&root )
- {
- if( root != NULL )
- {
- queue<BiTreeNode> que;
- que.push( *root );
- while( !que.empty() )
- {
- BiTreeNode curNode = que.front();
- que.pop();
- cout << curNode.data << " ";
- if( curNode.leftChild != NULL ) que.push( *curNode.leftChild );
- if( curNode.rightChild != NULL ) que.push( *curNode.rightChild );
- }
- }
- }
8.怎樣把一個連結串列掉個順序(也就是反序,注意連結串列的邊界條件並考慮空連結串列)?
這題主要看有沒有額外儲存空間的限制。
如果沒有,可以重新生成一個連結串列,該連結串列是原連結串列的反序。具體做的時候,每次只需把新節點插入的頭結點的前面即可。此時,空間複雜度O(n).
如果有儲存空間的限制,要求為O(1),即只能用常數個輔助變數。這時可以用三個指標來實現。首先,需要一個指標cur,指向要反向的節點。因為連結串列反序,指標要指向前一個,而單連結串列無法直接得到前一個,所以需要一個指標pre。然後,當指標cur反向後,就無法指向下一個,所以需要一個指標next,用於儲存cur的下一個。這樣只要遍歷整個連結串列,不斷使指標cur所指節點反向即可。
- struct ListNode
- {
- int data;
- ListNode *next;
- ListNode(): data(0), next(0) {};
- };
- //假設沒有哨兵元素
- ListNode* ReverseList( ListNode *head )
- {
- //空連結串列
- if( head == NULL ) return NULL;
- //只有一個元素的連結串列
- if( head->next == NULL ) return head;
- //至少有兩個元素
- ListNode *pre, *cur, *next;
- pre = head;
- cur = pre->next;
- next = NULL;
- while( cur != NULL )
- {
- //儲存下一個節點的指標
- next = cur->next;
- cur->next = pre;
- pre = cur;
- cur = next;
- }
- head->next = NULL;
- head = pre;
- return head;
- }
9.請編寫能直接實現int atoi(const char * pstr)函式功能的程式碼。
需要注意的問題:
1.前導白空
2.正負號
3.不同進位制
4.非法字元
5.Int範圍
- int MyAtoi(const char * pstr)
- {
- //去除前導空格
- while( *pstr == ' ' || *pstr == '\t' ) pstr++;
- //判斷正負號
- int sign = 1;
- if( *pstr == '+' || *pstr == '-' )
- {
- if( *pstr == '-' ) sign = -1;
- pstr++;
- }
- //判斷進位制
- int base = 10;
- if( *pstr == '0' )
- {
- pstr++;
- //以0開頭的為八進位制
- base = 8;
- //以0x開頭的為16進位制
- if( *pstr == 'X' || *pstr == 'x' )
- {
- base = 16;
- pstr++;
- }
- }
- //處理數值部分,注意非法字元
- long value = 0;
- while( *pstr != '\0' )
- {
- if( base == 10 && ( *pstr < '0' || *pstr > '9' ) ||
- base == 8 && ( *pstr < '0' || *pstr > '7' ) ||
- base == 16 && !( ( *pstr >= '0' && *pstr <= '9' ) ||
- ( *pstr >= 'A' && *pstr <= 'F' ) ||
- ( *pstr >= 'a' && *pstr <= 'f' ) )
- )
- return 0;
- value *= base;
- if( base == 16 )
- {
- if( *pstr >= '0' && *pstr <= '9' ) value += ( *pstr - '0' );
- if( *pstr >= 'a' && *pstr <= 'f' ) value += ( *pstr - 'a' ) + 10;
- if( *pstr >= 'A' && *pstr <= 'F' ) value += ( *pstr - 'A' ) + 10;
- }
- else
- {
- value += *pstr - '0';
- }
- pstr++;
- }
- //判斷是否溢位
- if( value > INT_MAX || value < INT_MIN ) return 0;
- return value * sign;
- }
10.程式設計實現兩個正整數的除法,當然不能用除法操作符。
// return x/y.
int div(const int x, const int y)
{
....
}
a/b=x, 即求a裡面有多少個b.
方法一:列舉,b*1,b*2,b*3,…,直到b*x == a 或 b*x < a && b*(x+1) > a,複雜度O( a/b)這樣
方法二:
除了x = 1+…+1(x個1相加),x還可以用2的冪的和表示(如4 = 2^2, 7 = 2^2+2+1 )。不用逐一列舉,類似折半查詢。不斷劃分割槽間,用區間比較。
不斷嘗試b*(1<<0),b*(1<<1),b*(1<<2),…,
直到b*(1<<m) < a && b*(1<<m+1) > a,
則從a - b*(1<<m),然後再重新開始。
- int Div( const int x, const int y )
- {
- if( x < y ) return 0;
- int tmp = x;
- int ans = 0;
- while( tmp >= y )
- {
- int cnt = 1;
- while( ( y * cnt ) <= tmp ) cnt <<= 1;
- cnt >>= 1;
- ans += cnt;
- tmp -= y * cnt;
- }
- return ans;
- }
11.在排序陣列中,找出給定數字的出現次數。比如[1, 2, 2, 2, 3] 中的出現次數是次。
方法一:直接遍歷,首先找到這個數,然後逐一計數,O(n)可完成。
方法二:二分查詢,首先找到這個數的第一個,記錄其位置。再二分查詢,找到這個數的最後一個,記錄其位置。最後下邊相減,O(lgn)可完成。雖然兩次都是二分查詢,但還是略微有點區別。
LowerSearch把相等的情況劃歸到左半部分,所以計算mid時要向下取整。
UpperSearch把相等的情況劃歸到右半部分,所以計算mid時要向上取整。
- //target出現的第一個位置
- int LowerSearch( int *pi, int left, int right, int target )
- {
- while( left < right )
- {
- //mid向下取整
- int mid = ( left + right ) / 2;
- if( target <= pi[mid] )
- {
- right = mid;
- }
- else
- {
- left = mid + 1;
- }
- }
- return left;
- }
- //target出現的第最後一個位置
- int UpperSearch( int *pi, int left, int right, int target )
- {
- while( left < right )
- {
- //這裡mid向上取整
- int mid = ( left + right + 1 ) / 2;
- if( target >= pi[mid] )
- {
- left = mid;
- }
- else
- {
- right = mid - 1;
- }
- }
- return left;
- }
- int GetCount( int *pi, int left, int right, int target )
- {
- int first = LowerSearch( pi, left, right, target );
- int second = UpperSearch( pi, left, right, target );
- return second-first+1;
- }
12.平面上N個點,每兩個點都確定一條直線,求出斜率最大的那條直線所通過的兩個點(斜率不存在的情況不考慮)。時間效率越高越好。
按照一般的方法,逐個求斜率比較,O(n^2)可完成。有沒有更快的方法?有。
對所有的點按x座標排序,然後只比較相鄰兩點的斜率即可。複雜度O( nlgn )。當然,只要有了演算法,程式設計實現很容易,關鍵是為什麼?
我不會嚴格的證明,只能樸素的理解一下。
設有三個點A、B、C
如果A、B、C在一條直線上,則斜率相等
如果A、B、C不在一條直線上,則構成三角形ABC。不妨設Xa < Xb < Xc
即按照x座標排序後,A、B相鄰,B、C相鄰。也就是說,三角形中AC為最長邊。如圖,顯然Kab和Kbc中至少有個大於Kac.
13.一個整數數列,元素取值可能是~65535中的任意一個數,相同數值不會重複出現。是例外,可以反覆出現。
請設計一個演算法,當你從該數列中隨意選取個數值,判斷這個數值是否連續相鄰。
注意:
- 5個數值允許是亂序的。比如:8 7 5 0 6
- 0可以通配任意數值。比如:7 5 0 6 中的可以通配成或者
- 0可以多次出現。
- 複雜度如果是O(n2)則不得分。
首先對這5個數進行排序。
如果5個數中沒有0,那麼用最大值 – 最小值。如果差值= 4,則連續。否則,不連續。
如果5個數中有0,則0必然排在最前面。依舊最大值 – 最小值。當差值取1,說明只有2個非0數,必然連續,則其餘的數都可用0補齊。那麼在連續的情況下差值最大取多少?最大值為4。這時必然有一個數不連續,但是可以用0補.
綜上:
1. 先排序
2. 用非零最大值 - 非零最小值,如果差值<=4,則連續。否則,不連續。
3. 處理沒有非零最大值或非零最小值的情況。
A. 全為零,必連續 B. 只用一個非0值,也連續
14.設計一個演算法,找出二叉樹上任意兩個結點的最近共同父結點。複雜度如果是O(n2)則不得分。
經典的LCA問題,有非常成熟的解法,用tarjan演算法或轉換為RMQ問題。Tarjan自己沒寫過。這裡是RMQ的解法。對於RMQ也有多種解法,比如線段樹、ST等。這裡討論一下ST演算法。
RMQ問題:RMQ( A, i, j )表示在陣列A中求A[i]…A[j]之間最小值的下標。
首先,把LCA轉換為RMQ問題。
對二叉樹進行DFS,記錄每個節點被訪問的順序。因為有回溯,除了根節點,每個節點都被訪問2次。設二叉樹有n個節點,則DFS完成後回記錄2n-1個節點,然後由這些節點構成陣列path,該數字記錄了DFS遍歷節點的順序。
在進行DFS時,同時記錄各節點的層數,組成陣列level。
對二叉樹上的任意兩點x和y, 找到x 、y在陣列path中第一次出現的位置,記為pos(x), pos(y)。則path[ pos(x) ]…path[ pos(y) ]代表在二叉樹上從x遍歷到y的一條路徑,那麼該路徑上level最小的點就是x 、y的LCA。
即LCA( A, i, j ) = RMQ( level, pos(x), pos(y) )
RMQ問題的ST求解。ST,實質上屬於DP。
定義:dp[i][j]表示數字A中,A[i]…A[i+2^j-1]中(即由A[i]開始的連續2^j個元素)最小值的下標
狀態轉換方程:dp[i][j] = Min( dp[i][j-1], dp[i+2^(j-1)][j-1] );
大概解釋一下:狀態方程把A[i]…A[i+2^j-1]共2^j個元素,分成兩部分A[i]…A[i+2^(j-1)-1]和A[[i+2^(j-1)]…A[j],每部分2^( j-1 )個元素,然後取兩部分的最小值即可。
上述部分,其實就是個DP的預處理過程。完成了預處理,最後就是RMQ問題的求解, RMQ( A, i, j ) = ?
有了上述的dp[][],只要想辦法把A[i]…A[j]分成兩部分,使每部分的長度為2^k。這樣就可以查dp[][]陣列了。對於這兩部分有什麼要求嗎?兩部分合起來剛好覆蓋整個[ i, j ]區間,這當然是最好的了。但是,有時很難取到整數,所以連部分通常是交叉的,甚至每一部分幾乎覆蓋了整個區間。
即,2^k = j - i + 1,則可求 k=lg( j-i+1 )。k是下取整。
最終:RMQ( A, i, j ) = Min( dp[i][k], dp[j-2^k+1][j] )
RMQ的ST求解見程式碼
- #include <iostream>
- using namespace std;
- const int MAX = 100;
- //dp[i][j] 表示從i開始到為i+2^j -1中值最小的一個值(從i開始2^j個數)
- //dp[i][j] = min( dp[i][j-1], dp[i+2^(j-1)][j-1] );
- //查詢RMQ( i, j )
- //將i,j分成兩個2^k個區間
- //k = log2( j - i + 1 )
- //查詢結果 min( dp[i][k], dp[j-2^k+1][k] )
- int dp[MAX][MAX];
- inline int Min( int x, int y )
- {
- return x < y ? x : y;
- }
- //使用DP,建立查詢表
- void MakeRmqIndex( int *data, int size )
- {
- int i, j;
- for( i=0; i<size; i++ )
- {
- dp[i][0] = i;
- }
- for( j=1; (1<<j)<size; j++ )
- {
- for( i=0; i+(1<<j)-1 < size; i++ )
- {
- dp[i][j] = data[ dp[i][j-1] ] < data[ dp[i+(1<<(j-1))][j-1] ] ? dp[i][j-1] : dp[i+(1<<(j-1))][j-1];
- }
- }
- }
- //查表,並返回結果
- int RmqIndex( int begin, int end, int *data )
- {
- int k = (int)( log( ( end - begin + 1 ) * 1.0 )/ log( 2.0 ) );
- return data[ dp[begin][k] ] < data[ dp[end-(1<<k)+1][k] ] ? dp[begin][k] : dp[end-(1<<k)+1][k];
- }
- int main()
- {
- int data[10] = { 1, 3, 3, 4, 5, 6, 6, 7, 9, 11 };
- //返回最小索引
- MakeRmqIndex( data, 10 );
- cout << RmqIndex( 4, 9, data) << endl;
- return 0;
- }
15.一棵排序二叉樹,令f=(最大值+最小值)/2,設計一個演算法,找出距離f值最近、大於f值的結點。複雜度如果是O(n2)則不得分。
16. 一個整數數列,元素取值可能是1~N(N是一個較大的正整數)中的任意一個數,相同數值不會重複出現。設計一個演算法,找出數列中符合條件的數對的個數,滿足數對中兩數的和等於N+1。複雜度最好是O(n),如果是O(n2)則不得分
這題要求O(n),我能想到就是:使用一個有N個元素的陣列,然後用數值作為陣列的下標,然後遍歷陣列。
轉自:http://blog.csdn.net/sj13051180/article/details/6727318
相關文章
- 經典面試題(二)附答案 演算法+資料結構+程式碼 微軟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小球問題演算法(原始碼)面試演算法原始碼