百度2014校招筆試題目題解
----武漢站,9.28號百度校招筆試題目算法題目部分
二、算法與程序設計題
1、給定任意一個正整數,求比這個數大且最小的“不重復數”,“不重復數”的含義是相鄰兩位不相同,例如1101是重復數,而1201是不重復數。(15分)
2、長度為N(N很大)的字符串,求這個字符串里的最長回文子串。(15分)
3、數軸上從左到右有n各點a[0], a[1], ……,a[n -1],給定一根長度為L的繩子,求繩子最多能覆蓋其中的幾個點。(15分)
這是百度的筆試題目,好像是什么系統行為分析師職位的筆試題目!博文最后會貼出試題照片!
題解:(題解非官方,僅供參考,轉載請聯系博主,有錯誤的地方望指正!謝謝)
1、給定任意一個正整數,求比這個數大且最小的“不重復數”,“不重復數”的含義是相鄰兩位不相同,例如1101是重復數,而1201是不重復數。
解: 這道題目我也沒有什么特別出彩的算法,就是按照常規思路來求解,首先要理解什么叫做“不重復數”,這是解題的關鍵之一,相鄰兩位不相同的數即為“不重復數”;還有一個地方要注意的就是“求比這個數大且最小的”,就是在比給定數大的不重復數中找到最小的!理解了這兩個地方,這道題目就可以着手求解了!
我用一個實例來說說我的思路,加入給定的正整數為11233:
用最常規的方法,就是每次對這個數加1,判斷是不是“不重復數”,加1后為11234,有兩個1重復了,於是繼續加1,……,那么主要就在判斷是不是“不重復數”上面了,我設置了兩個變量,是currentBit, lastBit,顧名思義,lastBit保存上一個數位的值,currentBit保存當前數位的值,拿整數12345來說,就是初始化lastBit = 5,然后計算currentBit = 4;下一步,把currentBit 值賦給lastBit,即lastBit = 4,計算currentBit = 3;依次類推。
說到這里,很多朋友覺得可以有更加高效的方法,小難點有4:
1、因為可以直接定位到“重復”的數位那里,比如12344,我們可以直接定位到個位數4和十位數4上面,然后在低位數(這里就是個位數)上加1即可,事實上,不是如此簡單,假設是對整數12199呢?還能簡單的加1操作嗎,加1之后結果為12200,那結果顯然是錯的,當然這也是可以處理的,處理方法就是把12200繼續拿到循環去判斷是不是“非重復數”;
2、這里又產生一個問題,因為“重復“的數位可能不止一對,有可能有多對,比如整數11233,定位“33”之后變為“34”,定位“11”之后變為“12”,結果就是12234。又有重復的地方,就是“22”。繼續拿到循環去重復;
3、還有要注意的是,你的程序設計,是如何存儲數位的值的,是一個一個數位的值求出來呢?還是像我這樣設置一個前后索引?每個數位取值,程序會變得繁瑣復雜,不易讀懂,如果是設置前后索引,則要注意程序退出循環的條件!
4、對於存在多對“重復”數位的正整數,數位“重復”的定位和變換,是從高位到低位,還是從低位到高位呢?正確的應該是從高位到低位,這與我們的程序設計也帶來了不便。
這些就是我在試圖找到高效算法時得到的經驗,每個小難點都要處理,有點繁瑣,有耐心的朋友,可以試着寫一下更高效的算法,另外,使用了比較高效的算法,代碼盡量保持簡潔,並告知博主,謝謝!
注:評論網友13樓“巴魯斯”給出了高效的C#算法代碼,這個算法我當初也考慮了,我主學C,只是C處理字符串沒有C#、Java那么方便,類型轉換也比較麻煩,就沒去管,十分感謝“巴魯斯”朋友的code!來自評論中24樓朋友“garbageMan”給出了比較簡潔的c語言代碼,采用遞歸方法,值得借鑒!再次感謝!
my code:(簡單加1的方法)

/* 給定任意一個正整數,求比這個數大且最小的“不重復數”,“不重復數的含義是相鄰兩位不相同,例如1101是不重復數” */ #include <stdio.h> #include <stdlib.h> int getNumNonrepetition(const int NumGived) { int flag = 0;//為0表示該數不是“不重復數” int numRepeat = NumGived; int numTemp = 0;// int currentBit = 0, lastBit = 0;//前后數位索引 while(1) { numRepeat++; //初始化后索引 numTemp = numRepeat; lastBit = numTemp % 10; numTemp /= 10; flag = 1; //判斷該數是不是“非重復數” while(numTemp != 0) { currentBit = numTemp % 10; numTemp /= 10; if(lastBit == currentBit) { flag = 0; break; } lastBit = currentBit; } if(flag == 1)//該數為不重復數,返回 { return numRepeat; } } } int main(void) { int NumGived = 19922884; int result = getNumNonrepetition(NumGived); printf("the number is %d\n", result); return 0; }
更新內容:(10.9號下午)
簡單加1的算法,效率太低,看到這么多的朋友的評論,大家的算法大同小異,我也寫了一個算法,拿出來和大伙分享。
算法:
1、把整數放到字符數組里面去,從高位為低位(用變量i)掃描,找到重復的數位,重復數位為“99”跳到第2步,否則跳到第3步,若沒有重復的數位,則該數為不重復數,返回;
2、遇到“99”的重復數,則把“99”改為“00”,然后在“99”前面一位字符加1,把掃描的地方定位在“99”往高位方向的第2位,比如是1299,變換后為1300,然后把掃描變量 i 定位在1這一數位上,返回第1步;
3、遇到非“99”的重復數,則直接在低位加1,后面依次變為010101……,結果就是最小的不重復數,返回改值;
至於前面說的一些難點,真是害怕誤導了大家,畢竟總有考慮不到的地方,希望見諒!
code:(高效)

#include <stdio.h> #include <stdlib.h> #include <string.h> #define SIZE 100 int getNumNonrepetition(const long long NumGived, char NumStr[]) { int NumTmp = NumGived; int NumLength = 0; int i = SIZE - 1; //把整數放到字符數組里面去,從后往前放,比如1234, //那么數組NumStr[96] = 1 NumStr[97] = 2 NumStr[98] = 3 NumStr[99] = 4, SIZE = 100 do { NumStr[i] = NumTmp % 10 + '0'; NumTmp /= 10; i--; }while(NumTmp != 0); NumLength = SIZE - i - 1;//計算整數的位數 int flag = 0;//設置010101的時候用的變量 i = SIZE - NumLength; while( 1 ) { //定位到重復的位上面,下標i + 1為低位,此時NumStr[i] == NumStr[i + 1] while(i + 1 < SIZE && NumStr[i] != NumStr[i + 1]) i++; if(i == SIZE - 1) break;//掃完一遍,沒有重復的,跳出循環,該數是不重復數 if(NumStr[i + 1] == '9')//重復的數位為99這種情況,將這兩位全部置0,高位加1 { NumStr[i + 1] = '0'; i--; NumStr[i + 1] = '0'; i--; NumStr[i + 1] += 1; } else//重復的 { //低位加1 NumStr[i + 1] += 1; i += 2; flag = 0; //后續全部設為0101……,這個時候肯定是不重復數了,所以可以跳出循環 while( i < SIZE ) { NumStr[i] = flag % 2+ '0'; flag++; i++; } break; } } //打印最小的”不重復數“ int start = SIZE - NumLength; //如果是99開頭的數字,高位可能會進位,判斷是否為零,不為零則有進位,需打印出來 if(NumStr[start - 1] != '0') putchar(NumStr[start - 1]); for(i = start; i < SIZE; i++ ) { putchar(NumStr[i]); } return 0; } int main(void) { long long NumGived = 119998988; char NumStr[SIZE]; memset(NumStr, '0', SIZE * sizeof(char)); getNumNonrepetition(NumGived, NumStr); return 0; }
2、長度為N(N很大)的字符串,求這個字符串里的最長回文子串。
解:題目指出“N很大”,就是提示我們不要想通過遍歷的方法來找到這個字符串,我想到的就一種解法,時間復雜度應該不高,但是我算不出來這個算法的復雜度是多少,首先說一下什么是回文字符串:回文字符串是指從左到右和從右到左相同的字符串,比如"1221"或者“12321”都是回文字符串。剛好舉得這兩個回文字符串的例子就是我的算法的兩個類別:
第一類“12321”:中間是一個單獨的字符。算法的思想是從第2個字符直到倒數第2個字符遍歷,每遇到一個字符,就依次判斷這個字符前后的字符是否相等,如果相等,則繼續判斷下一個字符,直到以這個字符為中心的兩邊對稱的字符不相等為止,或者前后字符的位置數組越界為止;計算此時的回文字符串的長度,與之前的比較,記下較長的回文字符串的長度和中心字符的位置;遍歷結束則返回最大長度和中心字符的位置。
圖示:若字符串為“1234321”
第二類“123321”:中間是兩個相同的字符。算法思想同上,其實是一樣的過程!圖解也是一樣的!
my code:

/* 長度為N(N很大)的字符串,求這個字符串里的最長回文子串。 */ #include <stdio.h> #include <stdlib.h> #include <string.h> //第一類“12321”:中間是一個單獨的字符 int FindLongPaliSubstr_Odd(const char A[], int * indexMid) { int i = 0, cnt = 0;//cnt表示前后移動位數 int MyMax = 0; int lenOfA = strlen(A); *indexMid = 0; for(i = 1; i <= lenOfA - 2; i++) { cnt = 0; while(i - cnt >= 0 && i + cnt <= lenOfA - 1 && A[i - cnt] == A[i + cnt]) { cnt++; } cnt--; //找到較大長度的回文字符串,保存中心字符的位置 if(MyMax < 2 * cnt + 1) { MyMax = 2 * cnt + 1; *indexMid = i; } } return MyMax; } //第二類“12321”:中間是兩個相同的字符。 int FindLongPaliSubstr_Even(const char A[],int * First) { int i = 0, cnt = 0;//cnt表示前后移動位數 int MyMax = 0; int lenOfA = strlen(A); *First = 0;//中間兩個相同字符的第一個字符位置 for(i = 0; i <= lenOfA - 2; i++) { if(A[i] == A[i + 1]) { cnt = 1; while(i - cnt >= 0 && (i + 1 + cnt) <= lenOfA - 1 && A[i - cnt] == A[i + 1 + cnt]) { cnt++; } cnt--; //找到較大長度的回文字符串,保存中心第一個字符的位置 if(MyMax < 2 * cnt + 2) { MyMax = 2 * cnt + 2; *First = i; } } } return MyMax; } int main(void) { char A[] = "adfadfbadfdg12321fagage"; int indexMid = 0; int First = 0; int i = 0; //兩種類別的最長回文子串的長度 int MaxOdd = FindLongPaliSubstr_Odd(A, &indexMid); int MaxEven = FindLongPaliSubstr_Even(A, &First); printf("indexMid = %d\n", indexMid); printf("First = %d\n", First); //哪類比較大,輸出哪一類的回文子串 if( MaxOdd > MaxEven) { for(i = indexMid - (MaxOdd - 1) / 2; i <= indexMid + (MaxOdd - 1) / 2; i++) { putchar(A[i]); } } else { for(i = First - (MaxEven - 2) / 2; i <= First + 1 + (MaxEven - 2) / 2; i++) { putchar(A[i]); } } return 0; }
3、數軸上從左到右有n各點a[0], a[1], ……,a[n -1],給定一根長度為L的繩子,求繩子最多能覆蓋其中的幾個點。
解:我對第3題的題解也是很常規,就是把相鄰兩個點的距離求出來,保存在一個數組arr[N]里面,從頭到尾遍歷數組arr[N],和直接選擇排序差不多,寫兩個for循環,第一個for循環(外循環)中計數器 i 表示連續線段的起點,第二個for循環(內循環)的計數器 j 從 (i + 1)開始,依次累加Sum,若Sum > L,則記錄點的個數(j - i)中的較大值max;其中,外循環,只要遇到的數比L大,就continue,內循環,只要遇到的數比L大,就break,這是因為長度為L的繩子是不可能覆蓋這些點的,可以直接跳過!
如圖:
my code:

/* 數軸上從左到右有n個點a[0],a[1],...,a[n - 1],給定一根長度為L的繩子,求繩子最多能覆蓋其中幾個點。 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define N 8 int MaxTimesOfL(int A[], int L) { int i = 0, j = 0; int *arr = (int *)malloc(sizeof(int) * (N - 1)); memset(arr, 0, sizeof(int) * (N - 1)); //初始化數組arr,兩點間的距離為一個數組元素 for(i = 0; i < N - 1; i++) { arr[i] = A[i + 1] - A[i]; } //輸出該數組 for(i = 0; i < N - 1; i++) { printf("%-3d", arr[i]); } int MaxTimes = 0; int Sum = 0; //遍歷找到覆蓋的最多點數 for(i = 0; i < N; i++) { if(arr[i] > L)//遇到比L大的數則跳過 { continue; } Sum = arr[i]; for(j = i + 1; j < N - 1; j++) { if(arr[j] > L)//遇到比L大的數則跳過,這一句對於程序來說加與不加都一樣 { break; } Sum += arr[j]; if(Sum > L) { break; } } MaxTimes = (MaxTimes > (j - i)) ? MaxTimes : (j - i); } return (MaxTimes + 1);//因為是線段,所以要加1表示覆蓋的點數 } int main(void) { int A[] = {-1, 0, 3, 9, 11, 13, 14, 25}; int L = 5; int result = MaxTimesOfL(A, L); printf("\nthe max times is %d\n", result); return 0; }
更新內容:(10月4日下午)
有網友指出,我的算法其實沒必要申請多余的數組,那么有沒有更加高效的算法呢,我身邊的一個大神給了我一個O(N)復雜度的算法:
他的原話:兩個指針,一個front,一個rear,每次front-rear,比L小,看覆蓋的點數。保存覆蓋點數的最大值,然后front++;比L大,rear++,每個數最多遍歷2遍,復雜度O(N)。
對於這個算法,他給了一個形象的比喻:
就好像一條長度為L的蛇。頭伸不過去的話,就把尾巴縮過來最多只需要走一次,就知道能覆蓋幾個點
實現代碼:

#include <stdio.h> #include <stdlib.h> int main(void) { int front = 0 , rear = 0;//設置首尾指針索引 int cnt = 8; int L = 15;//繩子長度 int MaxTimes = 0; int arr[] = {-1, 0, 3, 9, 11, 13, 14, 25};//數軸上的點 while(front < cnt) { //比L小,則計算MaxTimes,作front++; if(arr[front] - arr[rear] <= L) { MaxTimes = MaxTimes > (front - rear) ? MaxTimes : (front - rear); front++; } else//比L大,rear++; { rear++; } } printf("the max times is %d\n", MaxTimes + 1);//第一個數是沒有參與計數的,所以要在最后加1 return 0; }
最后貼一下試題: