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個元素的數組,然後用數值作爲數組的下標,然後遍歷數組。
1.正整數序列Q中的每個元素都至少能被正整數a和b中的一個整除,現給定a和b,需要計算出Q中的前幾項,
例如,當a=3,b=5,N=6時,序列爲3,5,6,9,10,12
(1)、設計一個函數void generate(int a,int b,int N ,int * Q)計算Q的前幾項
(2)、設計測試數據來驗證函數程序在各種輸入下的正確性。
感覺有點類似歸併排序的Merge。有兩個數組A、B。
數組A存放:3*1、3*2、3*3…
數組B存放:5*1、5*2、5*3…
有兩個指針 i, j,分別指向A、B的第一個元素。取Min( A[i], B[j] ),並將較小值的指針前移,然後繼續比較。
當然,編程實現的時候,完全沒有必要申請兩個數組,用兩個變量就可以。
- #include <iostream>
- using namespace std;
- void Generate( int a,int b,int N ,int * Q )
- {
- int tmpA, tmpB;
- int i = 1;
- int j = 1;
- for( int k=0; k<N; k++ )
- {
- tmpA = a * i;
- tmpB = b * j;
- if( tmpA <= tmpB )
- {
- Q[k] = tmpA;
- i++;
- }
- else
- {
- Q[k] = tmpB;
- j++;
- }
- }
- }
- int main()
- {
- int Q[6];
- Generate( 3, 5, 6 ,Q );
- return 0;
- }
2.有一個由大小寫組成的字符串,現在需要對他進行修改,將其中的所有小寫字母排在大寫字母的前面(大寫或小寫字母之間不要求保持原來次序),如有可能儘量選擇時間和空間效率高的算法c語言函數原型void proc(char *str)
也可以採用你自己熟悉的語言
應該類似快排的partition。快排的partition也有兩種常見的實現:從左往右掃描、從兩頭往中間掃描。這裏使用從左往後掃描的方式。
字符串在調整的過程中可以分成兩個部分:已排好的小寫字母部分、待調整的剩餘部分。用兩個指針i和j,其中i指向待調整的剩餘部分的第一個元素,用j指針遍歷待調整的部分。當j指向一個小寫字母時,交換i和j所指的元素。向前移動i、j,直到字符串末尾。
- #include <iostream>
- using namespace std;
- void Proc( char *str )
- {
- int i = 0;
- int j = 0;
- //移動指針i, 使其指向第一個大寫字母
- while( str[i] != '\0' && str[i] >= 'a' && str[i] <= 'z' ) i++;
- if( str[i] != '\0' )
- {
- //指針j遍歷未處理的部分,找到第一個小寫字母
- for( j=i; str[j] != '\0'; j++ )
- {
- if( str[j] >= 'a' && str[j] <= 'z' )
- {
- char tmp = str[i];
- str[i] = str[j];
- str[j] = tmp;
- i++;
- }
- }
- }
- }
- int main()
- {
- char data[] = "SONGjianGoodBest";
- Proc( data );
- return 0;
- }
3.如何隨機選取1000個關鍵字。
給定一個數據流,其中包含無窮盡的搜索關鍵字(比如,人們在谷歌搜索時不斷輸入的關鍵字)。如何才能從這個無窮盡的流中隨機的選取1000個關鍵字?
說實話我不會做,是看網上的答案。感覺是對的,但又說不上爲什麼。
思路是這樣的:
1.申請一個1000個元素的數組,用於保存最後選中的關鍵字
2.將數據流中前1000個直接放入數組中
3.對於第n個元素(n>1000), 以1000/n的概率隨機替換數組中的一個元素
這個就能保證每個元素都以1000/n的概率被選中。哎,爲什麼?先放這吧,以後再說。
4.判斷一個自然數是否是某個數的平方。說明:當然不能使用開方運算。
也就是判斷一個自然數是否是完全平方數。
方法一:從1開始逐個嘗試,即判斷1*1,2*2,3*3…,算法複雜度O( N^0.5 )
方法二:相當於在1…N之間找一個數x,使x*x = N。這樣看就是一個查找問題,所以用折半查找。算法複雜度O( logN )。
方法三:使用完全平方數的性質:每個完全平方數都可以表示成一系列奇數的和。
不妨這樣簡單理解一下:
設x是一個完全平方數,即 x = a^2,所以
a^2 = ( a – 1 +1 )^2 = (a-1)^2 + 2( a – 1 ) + 1
=( (a-2) + 1 )^2 + 2( a – 1 ) + 1
=(a-2)^2 + ( 2( a – 2 ) + 1 ) + (2( a – 1 ) + 1 )
即 x = 1 + 3 + 5 + … + (2( a – 1 ) + 1 )
故x可以表示爲一系列奇數的和.
因此判斷完全平方數的算法:x – 1 – 3 – 5…即從x中連續不斷的減去一個奇數,如果結果可以爲0,則x是完全平方數。否則,不是。算法複雜度O(N ),當然由於這裏做的全部是減法,可能也回比較快。
5.給定能隨機生成整數1到5的函數,寫出能隨機生成整數1到7的函數。
關鍵是要保證每個數字產生的概率相等。
把能隨機生成整數1到5的函數記爲R15。
我的想法是:把R15調用6次,然後統計這6次中,某個數字出現的次數。比如,統計1出現的次數。1的次數[0, 6],然後給次數加一,就可以隨機生成1到7之間的整數。
網上的解法:首先,調用7次R15。然後,取最大值對應的下標,由這些值構成了一個新數組。然後繼續調用R15,直到最後只剩下一個數字。
{ 1,2,3,4,5,6,7 }
5,3,1,5,2,4,5
{ 1, , ,4, , ,7 }
4, , ,1, , ,3
{ 1 }
6.1024! 末尾有多少個?
求末尾0個數,也就是對1024!進行因子分解,求因子中10的個數。在進一步,因子中10的個數,就相當與質因子中2*5的個數。因爲質因子5的個數比2少,所以也就是求1024!中質因子5的個數。
1,2,3,…,1024中哪些數都含有質因子5?主要有以下幾類:
第一類:5的倍數,1024/5 = 204個
第二類:25的倍數,1024/25 = 40個
第三類:125的倍數,1024/125 = 8個
第四類:625的倍數,1024/625 = 1個
則,總的因子5的個數:204 + 40 + 8 + 1 = 253
當然,爲什麼加起來就是最後的答案?這個不難,自己想想吧。
7. 有個海盜,按照等級從5到1排列,最大的海盜有權提議他們如何分享枚金幣。
但其他人要對此表決,如果多數反對,那他就會被殺死。
他應該提出怎樣的方案,既讓自己拿到儘可能多的金幣又不會被殺死?
(提示:有一個海盜能拿到98%的金幣)
很有意思的一個題。嘿嘿,不會做,也還是看網上答案的。
當有5個人時,等級爲5的海盜,等級最高,他來分配。分配時要考慮兩個問題:利益最大、不被殺死。至於他的分配方案會不會招來殺身之禍,完全取決於其他4個人的反應。所以考慮,4個人的情況。
當有4個人時,等級爲4的海盜,等級最高,他來分配。至於他的分配方案會不會招來殺身之禍,完全取決於其他3個人的反應。所以考慮,3個人的情況。
…
當有2個人時,等級爲2的海盜,等級最高,他來分配。這時他就可以肆無忌憚的分配了。分配方案:100,0。即給自己100枚金幣,給等級爲1的海盜0枚金幣。雖然對等級爲1的海盜來說很不公平,但是他反對也沒用,因爲只有兩個人,他佔不了大多數。
再來考慮三個人的問題。當有3個人時,等級爲3的海盜,等級最高,他來分配。他只要在前兩個人中爭取一個人就行。分配方案:99,0,1。這樣等級爲1的海盜肯定不會反對,因爲比2個人的時候分的多。只有等級爲2的海盜反對,但是沒有用
考慮四個人的情況。分配方案:99,0,1,0。等級爲4、2的海盜滿意。
五個人的情況。分配方案:98,0,1,0,1。
8.給定一個集合A=[0,1,3,8](該集合中的元素都是在,之間的數字,但未必全部包含),指定任意一個正整數K,請用A中的元素組成一個大於K的最小正整數。
比如,A=[1,0] K=21 那麼輸出結構應該爲100。
首先,計算正整數K的位數。假設k有m位。把用A中的元素組成一個大於K的最小正整數記爲x。那麼x就有m位或者m+1位。
根據K的最高位,在A中選數字。分兩種情況:A中的數字都比k的最高位小、A中至少有一個數字等於大於k的最高位。
1.A中的數字都比k的最高位小,則x有m+1位。這時,只要用A中的數字組成一個m+1位的最小正整數即可。
2.A中至少有一個數字等於大於k的最高位。這時x的最高位就是不小於K最高位的最小數字。然後,用同樣的方法繼續比較下一位。
編程實現:很煩,寫的都想吐血了。
- #include <iostream>
- #include <algorithm>
- using namespace std;
- //target爲int值,最多是10位數
- const int MAX_INT_CNT = 20;
- int NearestInt( int target, int *data, int size )
- {
- int ans = 0;
- //計算target的位數
- int cnt = 0;
- int tmp = target;
- while( tmp > 0 )
- {
- cnt++;
- tmp /= 10;
- }
- //將target轉換爲字符串
- char des[MAX_INT_CNT];
- itoa( target, des , 10 );
- string strTarget( des );
- //對數組排序
- sort( data, data+size );
- int flag = 0;
- int i, j;
- for( i=0; i<cnt; i++ )
- {
- ans *= 10;
- //遍歷數組,找到一個合適的元素
- for( j=0; j<size && flag==0; j++ )
- {
- if( strTarget[i] == data[j] )
- {
- ans += data[j];
- break;
- }
- if( strTarget[i] < data[j] )
- {
- ans += data[j];
- flag = 1;
- break;
- }
- }
- if( j >= size ) flag = 2;
- //flag == 2表示前面的數字都相等,只要後面的多一位就行
- if( flag == 2 )
- {
- if( i == 0 )
- {
- //找到一個非0元素
- for( j=0; j<size; j++ )
- {
- if( data[j] > 0 )break;
- }
- ans += data[j];
- }
- else
- ans += data[0];
- }
- //flag == 1表示前面的數字比較大,後面的取最小的數字即可
- if( flag == 1 ) ans += data[0];
- }
- //如果前面的數字都相等
- if( flag == 2 )
- {
- ans *= 10;
- ans += data[0];
- }
- return ans;
- }
- int main()
- {
- int data[] = { 0, 1, 3, 8 };
- cout << NearestInt( 21, data, 4 ) << endl;
- return 0;
- }
9. 用C語言實現一個revert函數,它的功能是將輸入的字符串在原串上倒序後返回。
基本的字符串操作。應該沒有什麼問題,比起鏈表的反轉簡單多了。
- char* Revert( char *str )
- {
- if( str != NULL )
- {
- char *begin = str;
- char *end = str;
- while( *end != '\0' ) end++;
- end--;
- while( begin != end )
- {
- char tmp = *begin;
- *begin = *end;
- *end = tmp;
- begin++;
- end--;
- }
- }
- return str;
- }
10.用C語言實現函數void * memmove(void*dest, const void *src, size_t n)。memmove函數的功能是拷貝src所指的內存內容前n個字節到dest所指的地址上。
其實就是自己寫一個memcpy函數。注意下面三種情況:
指針爲空
兩個指針間距過小( 如dest = 10010, src =10020, n = 20 )
void*的轉換
- void* Memmove( void *dest, const void *src, size_t n )
- {
- char *cDest = (char*) dest;
- char *cSrc = (char*) src;
- assert( cDest != NULL && cSrc != NULL );
- assert( cDest >= cSrc + n || cSrc >= cDest + n );
- while( n-- ) *cDest++ = *cSrc++;
- return dest;
- }
11.有一根釐米的細木杆,在第3釐米、7釐米、11釐米、17釐米、23釐米這五個位置上各有一隻螞蟻。木杆很細,同時只能通過一隻螞蟻。開始時,螞蟻的頭朝左還是朝右是任意的,它們只會朝前走或調頭,但不會後退。當任意兩隻螞蟻碰頭時,兩隻螞蟻會同時調頭朝反方向走。假設螞蟻們每秒鐘可以走一釐米的距離。
編寫程序,求所有螞蟻都離開木杆的最小時間和最大時間。
不知這題是想考什麼。
題目的難點在於:初始狀態,螞蟻的方向任意。因爲只有5個螞蟻,每隻螞蟻的方向只有左、右兩種選擇,因此5只螞蟻的初始方向有2^5 = 32種情況。
沒有想到什麼好的算法,只能枚舉所有情況。對每種情況,模擬螞蟻的爬杆過程:沿初始方向前進、每秒更新一次螞蟻的位置、更新完成後進行碰撞檢測。當所有螞蟻都爬出細杆後,就可以得到所需時間。最後,在所有的初始情況下,求最小時間和最大時間。索性數據量很小,時間可以接受。
- const int LEFT = 0;
- const int RIGHT = 1;
- //記錄每個螞蟻的初始方向
- int dir[5];
- //記錄每個螞蟻的初始位置
- int pos[5];
- //記錄每個螞蟻是否爬出了細杆
- bool isFinish[5];
- void Init( int i )
- {
- //初始化螞蟻的方向
- int tmp = i;
- int mask = 0x0001;
- for( int j=0; j<5; j++ )
- {
- dir[j] = ( tmp & mask ) ? RIGHT : LEFT;
- tmp >>= 1;
- }
- //初始化螞蟻的位置
- pos[0] = 3;
- pos[1] = 7;
- pos[2] = 11;
- pos[3] = 17;
- pos[4] = 23;
- //初始化螞蟻的狀態標誌
- memset( isFinish, false, sizeof(isFinish) );
- }
- void AntTime( int &maxTime, int &minTime )
- {
- int max = 0;
- int min = 10000000;
- //依次處理32種情況
- for( int i=0; i<32; i++ )
- {
- Init( i );
- //記錄已經爬出細杆的螞蟻個數
- int cnt = 0;
- //每秒檢測一次
- int time;
- for( time=1; ; time++ )
- {
- //更新螞蟻位置
- for( int j=0; j<5; j++ )
- {
- if( !isFinish[j] )
- {
- if( dir[j] == LEFT )
- pos[j]--;
- else
- pos[j]++;
- }
- }
- //檢測螞蟻是否已爬出細杆
- for( int m=0; m<5; m++ )
- {
- if( !isFinish[m] && ( pos[m] < 0 || pos[m] > 23 ) )
- {
- isFinish[m] = true;
- cnt++;
- }
- }
- //如果所有的螞蟻都已經爬出細杆,則跳出
- if( cnt >= 5 ) break;
- //如果相撞,則掉頭
- for( int k=0; k<5; k++ )
- {
- if( !isFinish[k] )
- {
- if( ( k == 0 && pos[k] == pos[k+1] ) || ( k == 5 && pos[k] == pos[k-1] ) ||
- ( ( k > 0 && k < 5 ) && ( pos[k] == pos[k+1] || pos[k] == pos[k-1] ) )
- )
- {
- dir[k] = ( dir == LEFT ) ? RIGHT : LEFT;
- }
- }
- }
- }
- if( time > max ) max = time;
- if( time < min ) min = time;
- }
- maxTime = max;
- minTime = min;
- }
12.請定義一個宏,比較兩個數a、b的大小,不能使用大於、小於、if語句
這裏有兩種做法:正數的絕對值等於本身、兩數相減判斷符號位
- #define MAX( a, b ) ( fabs( a, b ) == ( (a) - (b) ) ? (a) : (b) )
- #define MMAX( a, b ) ( ( ( (a) - (b) ) & ( 1 << 31 ) ) ? (a) : (b) )
13.兩個數相乘,小數點後位數沒有限制,請寫一個高精度算法
14.有A、B、C、D四個人,要在夜裏過一座橋。他們通過這座橋分別需要耗時1、2、5、10分鐘,只有一支手電,並且同時最多隻能兩個人一起過橋。請問,如何安排,能夠在17分鐘內這四個人都過橋?
這題想想不難,就不知道具體編程應該怎麼實現,能想到的就是DFS。這裏的17分鐘應該就是最短時間了。先不管編程實現了,說說具體的思路吧
首先,要到對岸,每次不能只過一個人。因爲這個人拿了手電,其他人都過不了。這樣,每次過橋,必須兩個人。兩個人過去,其中一個人再拿了手電回來。那選哪兩個人過去,哪個人再回來?當然是時間最小的啦。所以,5分鐘的人和10分鐘的人結伴過河,這樣可以把5分鐘的時間淹沒在10分鐘內,共需10鍾就可以完成。在讓時間最小的人拿了手電回去,那自然選1分鐘的人了。也就是說,1分鐘的人必須在5、10之前到達對岸。
這樣,整個過程就是:1、2先到對岸(2Min),2拿了手電返回(2Min),5、10再結伴過橋(10Min),1拿手電返回(1Min),最後1、2結伴過橋(2Min),總共剛好17分鐘。
15.有12個小球,外形相同,其中一個小球的質量與其他11個不同,給一個天平,問如何用3次把這個小球找出來,並且求出這個小球是比其他的輕還是重
很久以前的題了,估計大多數人都見過。類似折半查找的方法,把問題的規模以O( lgn )的速度減小。12---6---3---1。當剩3個時,問題最精妙,這時有三種狀態可利用:天平左半、天平右邊、不在天平兩端。這提示我們,其實27個小橋也可以用這個方法。27---9----3----1,即稱3次就可以完成。
其實,這裏可以總結一個規律:( 3^(n-1), 3^n ]內的數都只需n次就可以完成。即,10、11、12、….、27個球都只用3次就可以。
16.在一個文件中有10G 個整數,亂序排列,要求找出中位數。內存限制爲2G。只寫出思路即可。
海量數據處理的問題。10G個數,中位數就是第5G、第5G+1個數。回想一下,一般情況下求中位數的做法:類似於快排的partition,找到一個數,使比它小的數的個數佔到總數的一半就行。所以,可以把數值空間分段,然後統計每一段中數據的個數,這樣就可以很容易的確定中位數在那一段。找個該段後,數據量已經急劇減小了,剩下的問題就好處理了。這種方法可以說是桶排序的思想,也可以說是hash的思想。下面具體分析一下:
因爲要統計每一段中數據的個數,所以可以用一個unsigned int型。unsigned int一般佔4個字節,可以計數到2^32-1,大約是4G。題目中有10G個數,如果有很多數落在同一個段中,unsigned int肯定不夠用。所以,這裏的計數用要8字節的long long。即,相當於有一個數組,數組是long long性,數組的每一個元素,代表了一個數據段內的數據個數。這個數組有多大?爲了充分利用2G內存,數組大小2G/8 = 256M。即,有數組long long cnt[256M].
假設題目中的10G個數都是4字節的int。如何把這10G個整數,映射到cnt[256M]的數組中。可以使用計算機中的虛擬地址到物理地址的轉換。取int的高28位作爲數組下標的索引值,這樣就可以完成映射。
整個算法的流程:
掃描10G個整數,對每個整數,取高28位,映射到數組的某個元素上
給數組的這個元素加1,表示找到一個屬於該數據段的元素
掃描完10G個整數後,數組cnt中就記錄了每段中元素的個數
從第一段開始,將元素個數累計,直到值剛好小於5G,則中位數就在該段
這時對10G個整數再掃描一遍,記錄該段中每個元素的個數。直至累計到5G即可。
17..一個文件中有40億個整數,每個整數爲四個字節,內存爲1GB,寫出一個算法:求出這個文件裏的整數裏不包含的一個整數
方法一:
使用位圖。4字節的int,有4G個不同的值。每個值,對應1bit,則共需4G/8 = 512M
內存。初始狀態,對512M的位圖清零。然後,對這40億個整數進行統計。如果某個值出現了,那麼就把這個值對應的bit置位。最後,掃描位圖,找到一個沒有被置位的bit即可。
方法二:
分段統計。Long long cnt[512M/8=64M]對應數值空間的64M個數據段。每個數據段包含64個不同值,用一個long long作爲這個數據段內的位圖,位圖佔64M*8=512M。
這樣掃描一遍40億個整數後,從數組中找到一個計數小於64的元素,然後查看它的位圖,找出未出現的元素。
方法二平均性能應該比方法一快,但它佔的內存很恐怖。其實,這兩種方法都不是很實際,總共1G的內存,算法就消耗512M甚至1G,那剩下的系統程序怎麼辦?OS都跑不起來了吧。
18.騰訊服務器每秒有2w個QQ號同時上線,找出5min內重新登入的qq號並打印出來。
這應該是道面試題,面試官隨口問了一下。主要是看思路吧。
最簡單的想法:直接用STL的set。從某一時刻開始計時,每登陸一個QQ,把它放入set,如果已存則直接打印。直到5min後,就可以over了。下面來簡單分析一下算法的負複雜度:
空間複製度:用str存儲每個QQ號,假設QQ號有20位,理想情況下每個QQ佔20Byte。則5min內的QQ:2w * 60 * 5 = 600w個,需要的存儲空間600w * 20byte = 12000w byte = 120M,這樣的存儲應該可以忍受吧。
時間複雜度:STL的set是用二叉樹(更確切的說是:紅黑樹)實現的,查找效率是O( lgn ),應該還是挺快的吧。
呃,有人說不讓用STL。那就自己設計一個數據結構唄。該用什麼數據結構呢?想了想,還是繼續用樹,這裏用一個trie tree吧。節點內容包括QQ號、指向子節點的指針(這裏有10個,認爲QQ由0---9的數字組成)。登陸時間要不要?考慮這樣一個問題:是否需要把所有的QQ都保存在內存中?隨着時間的增加,登陸的QQ會越來越多,比較好的方法是把長時間不登陸的QQ釋放掉。所以需要記錄登陸時間,以便於釋放長期不登陸的QQ。
- struct TrieNode
- {
- string qq;
- int lastLoginTime;
- TrieNode *next[10];
- };
我們的trie上的操作主要有兩個:查找並插入、刪除。也就是說,這顆樹是不斷動態變化的,我們需要維護它。