寫在前面
本篇部落格主要是解答這次校招中京東的筆試程式設計題,這次京東的筆試程式設計題比較難,涉及KMP演算法、manacher演算法等。文中的解法也是在觀看了左神(左程雲)9月20號在牛客網的直播後,自己花時間寫出來的。本篇部落格不涉及演算法的具體分析,主要是解題程式碼及簡單的思路,關於其中的一些演算法我會在後面的部落格中詳細介紹。
題目及解答
題目一
題目描述:
東東從京京那裡瞭解到有一個無限長的數字序列: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, ...(數字k在該序列中正好出現k次)。東東想知道這個數字序列的第n項是多少,你能幫幫他麼。
輸入描述:
輸入包括一個整數n(1 ≤ n ≤ $10^{18}$) 。
輸出描述:
輸出一個整數,即數字序列的第n項。
示例:
輸入
169
輸出
18
解題思路:
這個主要是等差數列的求和,假設給的輸入為k,則
$n(n-1)/2 < k < n(n+1)/2$,化簡得到$\frac{1+\sqrt{1+8k}}{2} > 0$,即求上式滿足時的最小正整數。同時此題要注意數的範圍。
具體程式碼:
public long findNum(long input){
return (long)Math.ceil((Math.sqrt(1 + 8 * input) -1)/2);
}複製程式碼
題目二
題目描述:
東東在一本古籍上看到有一種神奇數,如果能夠將一個數的數字分成兩組,其中一組數字的和等於另一組數字的和,我們就將這個數稱為神奇數。例如242就是一個神奇數,我們能夠將這個數的數字分成兩組,分別是{2,2}以及{4},而且這兩組數的和都是4。東東現在需要統計給定區間中有多少個神奇數,即給定區間[l, r],統計這個區間中有多少個神奇數,請你來幫助他。
輸入描述:
輸入包括一行,,一行中兩個整數l和r(1 ≤ l,r ≤ $10^9$,0 ≤ r - l ≤ $10^6$),以空格分割。
輸出描述:
輸出一個整數,即區間內的神奇數個數。
示例:
輸入
1 50
輸出
4
解題思路:
這個題主要是判斷一個數是不是神奇數,且時間複雜度儘量低。一個數判斷神奇數的問題其實是一個揹包問題,比如242這個數,就是陣列arr[2,2,4]中每個數可以用也可以不用,是否能組成sum(arr) = 8的一半,能則是神奇數,反之則不能。還有如果和為奇數則也不能。
具體程式碼:
public int findMagicNumber(int start, int end) {
int ans = 0;
int[] digitals = new int[10];
boolean[] dp = new boolean[9 * 9];
Arrays.fill(digitals, -1);
Arrays.fill(dp, false);
dp[0] = true;
for (int i = start; i <= end; i++) {
int num = i;
int sum = 0;
int index = 0;
while (num > 0) {
int temp = num % 10;
digitals[index++] = temp;
sum += temp;
num = num / 10;
}
// 當各位數字和為偶數時,才存在神奇數
if ((sum & 1) == 0){
for (int j = 0; j < digitals.length && digitals[j] != -1; j++) {
for (int k = sum; k >= 0; k--) {
// 用一維陣列進行更新
dp[k] = dp[k] || j == 0 ? sum == digitals[j] : (k - digitals[j] < 0 ? false : dp[k-digitals[j]]);
}
if(dp[sum / 2]) {
ans++;
break;
}
}
}
Arrays.fill(digitals, -1);
Arrays.fill(dp, false);
dp[0] = true;
}
return ans;
}複製程式碼
題目三
題目描述:
給定一個字串s,請計算輸出含有連續兩個s作為子串的最短字串。注意兩個s可能有重疊部分。例如,"ababa"含有兩個"aba"。
輸入描述:
輸入包括一個字串s,字串長度length(1 ≤ length ≤ 50),s中每個字元都是小寫字母。
輸出描述:
輸出一個字串,即含有連續兩個s作為子串的最短字串。
示例:
輸入
abracadabra
輸出
abracadabracada
解題思路:
這個題目是關於KMP演算法中求next陣列的問題,我們只需求出字串最後一個字元後一位的next值。這個能算出來這個題目就解決了。
具體程式碼:
public String shortestRepeatString(String str){
int[] next = new int[str.length() + 1];
Arrays.fill(ne xt, 0);
next[0] = -1;
next[1] = 0;
for (int i = 2; i < next.length; i++) {
char pre = str.charAt(i - 1);
int k = next[i - 1];
while (k != -1){
if (str.charAt(k) == pre) {
next[i] = next[i - 1] + 1;
break;
}
k = next[k];
}
}
return str + str.substring(next[str.length()]);
}複製程式碼
題目四
題目描述:
合法的括號匹配序列被定義為:
- 空串""是合法的括號序列。
- 如果"X"和"Y"是合法的序列,那麼"XY"也是一個合法的括號序列。
- 如果"X"是一個合法的序列,那麼"(X)"也是一個合法的括號序列。
- 每個合法的括號序列都可以由上面的規則生成。
例如"","()","()()()", "(()())","(((())))"都是合法的。 東東現在有一個合法的括號序列s,一次移除操作分為兩步: - 移除序列s中第一個左括號。
- 移除序列s中任意一個右括號,保證操作之後s還是一個合法的括號序列。
東東現在想知道使用上述的移除操作有多少種方案可以把序列s變為空。
如果兩個方案中有一次移除操作移除的是不同的右括號就認為是不同的方案。
例如: s = "()()()()()",輸出1,因為每次都只能選擇被移除的左括號所相鄰的右括號。
s = "(((())))",輸出24,第一次有4種情況,第二次有3種情況,... ,依次類推,4 3 2 1 = 24。
*輸入描述:
輸入包括一行,一個合法的括號序列s,序列長度length(2 ≤ length ≤ 20)。
輸出描述:
輸出一個整數,表示方案數。
示例:
輸入
(((())))
輸出
24
解題思路:
從右往左遍歷,遇到左括號看左括號右邊右括號數量與左括號數量的差,並將這些差乘起來,即為方案數。
具體程式碼:
public int removeSolutions(String str){
int ans = 1;
int count = 0;
for (int i = str.length() - 1; i >= 0; i--) {
if (str.charAt(i) == ')') {
count++;
} else {
ans *= count;
count--;
}
}
return ans;
}複製程式碼
題目五
題目描述:
京京和東東是好朋友。東東很喜歡迴文。迴文是指從前往後讀和從後往前讀是一樣的詞語。京京準備給東東一個驚喜,先取定一個字串s,然後在後面附上0個或者更多個字母形成迴文,京京希望這個迴文越短越好。請幫助京京計算他能夠得到的最短的迴文長度。
輸入描述:
輸入包括一個字串s,字串s長度length(1 ≤ length ≤ 50)。
輸出描述:
輸出一個整數,表示京京能夠得到的最短的迴文長度。
示例:
輸入
abab
輸出
5
解題思路:
這個題目主要是manacher(馬拉車)演算法的變形,主要是要求以最後一個字元結尾的時候的最大回文子串。
具體程式碼:
public int longestPalindrome(String str){
int ans = 0;
// 字串預處理
String strend = "$#";
for (int i = 0; i < str.length(); i++) {
strend += str.charAt(i);
strend += "#";
}
strend += "@";
// manacher演算法
int[] radius = new int[strend.length()];
Arrays.fill(radius, 0);
int right = 0; //迴文最右邊界
int center = 0; //迴文中心
int left = 0; //迴文左邊界
for (int i = 1; i < strend.length(); i++) {
radius[i] = right > i ? Math.min(radius[2 * center - i], right - i): 1;
while (strend.charAt(i + radius[i]) == strend.charAt(i - radius[i])) {
radius[i] += 1;
}
if (right < i + radius[i]) {
right = i + radius[i];
center = i;
}
if(right == strend.length() - 1){
break;
}
}
return 2*str.length() - (radius[center] * 2 - 1)/2;
}複製程式碼
歡迎大家交流和批評指正!
ps:本文有些公式顯示不正確,可以訪問京東2018校招程式設計題解答(Java)