寫在前面
- 本系列包含《劍指Offer》66道算法題,預計一週刷完,這是第四篇。
- 所有題目均可在牛客網在線編程平臺進行調試。
網址:https://www.nowcoder.com/ta/coding-interviews - 本系列包含題目,解題思路及代碼(Java)。
代碼同步發佈在GitHub:https://github.com/JohnnyJYWu/offer-Java
上一篇:算法 | 一週刷完《劍指Offer》 Day3:第27~37題
下一篇:算法 | 一週刷完《劍指Offer》 Day5:第50~58題
Day4:第38~49題
有幾道題比較考邏輯和理解。到後面就簡單了,好像更注重思維了。
- T38. 二叉樹的深度
- T39. 平衡二叉樹
- T40. 數組中只出現一次的數字
- T41. 和爲S的連續正數序列
- T42. 和爲S的兩個數字
- T43. 左旋轉字符串
- T44. 翻轉單詞順序列
- T45. 二叉樹的深度
- T46. 孩子們的遊戲(圓圈中最後剩下的數)
- T47. 求1+2+3+…+n
- T48. 不用加減乘除做加法
- T49. 把字符串轉換成整數
T38. 二叉樹的深度
題目描述
輸入一棵二叉樹,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,最長路徑的長度爲樹的深度。
解題思路
分別對左右子樹遞歸計算深度,取深度更大的一個。
public int TreeDepth(TreeNode root) {
if(root == null) return 0;
return 1//當前節點深度1
+ Math.max(TreeDepth(root.left), TreeDepth(root.right));//左右子樹取深度更大的值
}
T39. 平衡二叉樹
題目描述
輸入一棵二叉樹,判斷該二叉樹是否是平衡二叉樹。
解題思路
平衡二叉樹:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。
此題即將上一題計算左右子樹深度【取更大】的思想轉化爲了計算左右子樹深度【判斷差值是否不超過1】。
public boolean IsBalanced_Solution(TreeNode root) {
if(root == null) return true;
return getDepth(root) != -1;
}
private int getDepth(TreeNode root) {
if(root == null) return 0;
int left = getDepth(root.left);
if(left == -1) return -1;
int right = getDepth(root.right);
if(right == -1) return -1;
return Math.abs(left - right) > 1 ? -1 : 1 + Math.max(left, right);
}
}
T40. 數組中只出現一次的數字
題目描述
一個整型數組裏除了兩個數字之外,其他的數字都出現了偶數次。請寫程序找出這兩個只出現一次的數字。
解題思路
重點:注意題意,兩個數只出現一次,其他數都出現偶數次。
方法一:HashSet。不包含則加入,包含則移除。最終出現偶數次的數一定都會被移除,僅留只出現了一次的數。
//num1,num2分別爲長度爲1的數組。傳出參數
//將num1[0],num2[0]設置爲返回結果
public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) {//HashSet
Set<Integer> set = new HashSet<>();
for(int num: array) {
if(set.contains(num)) {
set.remove(num);
} else {
set.add(num);
}
}
Iterator<Integer> it = set.iterator();
num1[0] = it.next();
num2[0] = it.next();
}
方法二:異或運算。此方法比較巧妙,需仔細理解。
首先理解二進制位運算的幾個概念:
與運算( & ):運算規則:0&0=0; 0&1=0; 1&0=0; 1&1=1;
或運算( | ):0|0=0; 0|1=1; 1|0=1; 1|1=1;
異或運算( ^ ):運算規則:0^0=0; 0^1=1; 1^0=1; 1^1=0;
異或運算有此特性:n^n=0;即任意數與本身異或得0;
所以對array數組所有數進行異或運算,得到的結果爲兩個僅出現一次的數異或運算的結果different(其他數由於出現偶數次,異或運算後都爲0了),這個different是區分這兩個數不同的標誌。
然後,或運算有此特性:n&-n取得n的二進制表示中最右邊的1。(-n的表示百度補碼)
對different進行與運算:different = different & (-different)。此時的different就成爲了那個標誌。
最後再對array數組所有數進行一次異或運算,不同的是這一次要根據different將兩個數區分開,填入結果。
//num1,num2分別爲長度爲1的數組。傳出參數
//將num1[0],num2[0]設置爲返回結果
public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) {//位運算
int different = 0;
for(int num: array) {//最終得到兩個只出現一次的數相異的的結果
different ^= num;
}
//得到最右邊的1
different &= - different;
for(int num: array) {
//由於different是那兩個數像異的結果,那麼這個取得的最右邊的1,可將兩個只出現一次的數區分開
//然後再進行一遍異運算即可
if((num & different) == 0) {
num1[0] ^= num;
} else {
num2[0] ^= num;
}
}
}
方法三:暴力解,不多贅述了。
//num1,num2分別爲長度爲1的數組。傳出參數
//將num1[0],num2[0]設置爲返回結果
public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2) {//暴力解法
ArrayList<Integer> list = new ArrayList<Integer>();
Arrays.sort(array);
int length = array.length;
for(int i = 0; i < length; i ++){
if(i == length - 1 && array[i] != array[i - 1]) {
list.add(array[i]);
}else if(i == 0 && array[i] != array[i + 1]) {
list.add(array[i]);
}else{
if(i != 0 && i != length - 1 && array[i] != array[i - 1] && array[i] != array[i + 1]) {
list.add(array[i]);
}
}
}
num1[0] = list.get(0);
num2[0] = list.get(1);
}
T41. 和爲S的連續正數序列
題目描述
小明很喜歡數學,有一天他在做數學作業時,要求計算出9~16的和,他馬上就寫出了正確答案是100。但是他並不滿足於此,他在想究竟有多少種連續的正數序列的和爲100(至少包括兩個數)。沒多久,他就得到另一組連續正數和爲100的序列:18,19,20,21,22。現在把問題交給你,你能不能也很快的找出所有和爲S的連續正數序列? Good Luck!
輸出描述:輸出所有和爲S的連續正數序列。序列內按照從小至大的順序,序列間按照開始數字從小到大的順序
解題思路
夾逼思想,定義正數序列的左邊界small和右邊界big,求small到big的和。和比所求小則big後移,比所求大則small後移。
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {//算法,夾逼
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
if(sum <= 1) return result;
int small = 1;
int big = 2;
while(small < (sum + 1) / 2) {//要求最少是兩個數字,所以small最大爲(s+1)/2
int curSum = 0;//求small到big的和
for(int i = small; i <= big; i ++) {
curSum += i;
}
//夾逼思想,小則big後移,大則small後移
if(curSum < sum) {
big ++;
} else if(curSum > sum) {
small ++;
} else {
ArrayList<Integer> list = new ArrayList<>();
for(int i = small; i <= big; i ++) {
list.add(i);
}
result.add(list);
small ++;
}
}
return result;
}
公式求解。
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {//公式
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
if(sum < 3) return result;
for(int i = 1; i <= sum / 2; i ++) {
int value = 1 + 4 * i * i - 4 * i + 8 * sum;
int valueSqrt = (int) Math.sqrt(value);
if(value >= 25 && valueSqrt * valueSqrt == value) {
ArrayList<Integer> list = new ArrayList<Integer>();
for(int j = i; j <= (valueSqrt - 1) >> 1; j ++) {
list.add(j);
}
result.add(list);
}
}
return result;
}
T42. 和爲S的兩個數字
題目描述
輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,如果有多對數字的和等於S,輸出兩個數的乘積最小的。
輸出描述:對應每個測試案例,輸出兩個數,小的先輸出。
解題思路
夾逼思想,且和相等時,差越大乘積越小。因此從兩端向中間夾逼時第一個和爲S的即爲所求。
public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
//夾逼思想,且和相等時,差越大乘積越小
ArrayList<Integer> result = new ArrayList<>();
int small = 0;//數組下標,從前往後移
int big = array.length - 1;//數組下標,從後往前移
while(small <= big) {
int curSum = array[small] + array[big];
if(curSum < sum) {
small ++;
} else if(curSum > sum) {
big --;
} else {
result.add(array[small]);
result.add(array[big]);
return result;
}
}
return result;
}
T43. 左旋轉字符串
題目描述
彙編語言中有一種移位指令叫做循環左移(ROL),現在有個簡單的任務,就是用字符串模擬這個指令的運算結果。對於一個給定的字符序列S,請你把其循環左移K位後的序列輸出。例如,字符序列S=”abcXYZdef”,要求輸出循環左移3位後的結果,即“XYZdefabc”。是不是很簡單?OK,搞定它!
解題思路
分三步走。(交換步驟順序也可)
step1:先將左邊3個字符串進行翻轉:[abc]XYZdef --> [cba]XYZdef
step2:再將右邊剩餘字符串進行翻轉:cba[XYZdef] --> cba[fedZYX]
step3:最後將整個字符串進行翻轉: cbafedZYX --> XYZdefabc
public String LeftRotateString(String str, int n) {
if(str == null || str.length() == 0) return "";
char[] chars = str.toCharArray();
//step1:先將左邊3個字符串進行翻轉:[abc]XYZdef --> [cba]XYZdef
reverse(chars, 0, n - 1);
//step2:再將右邊剩餘字符串進行翻轉:cba[XYZdef] --> cba[fedZYX]
reverse(chars, n, chars.length - 1);
//step3:最後將整個字符串進行翻轉: cbafedZYX --> XYZdefabc
reverse(chars, 0, chars.length - 1);
return new String(chars);
}
private void reverse(char[] chars, int start, int end) {
while(start < end) {
char tmp = chars[start];
chars[start] = chars[end];
chars[end] = tmp;
start ++;
end --;
}
}
T44. 翻轉單詞順序列
題目描述
牛客最近來了一個新員工Fish,每天早晨總是會拿着一本英文雜誌,寫些句子在本子上。同事Cat對Fish寫的內容頗感興趣,有一天他向Fish借來翻看,但卻讀不懂它的意思。例如,“student. a am I”。後來才意識到,這傢伙原來把句子單詞的順序翻轉了,正確的句子應該是“I am a student.”。Cat對一一的翻轉這些單詞順序可不在行,你能幫助他麼?
解題思路
原理同上一題。先翻轉每個單詞的順序,再翻轉整個句子的順序。(交換步驟順序也可)
public String ReverseSentence(String str) {//原理同T43
if(str == null || str.length() == 0) return str;
char[] chars = str.toCharArray();
int length = chars.length;
int startIndex = 0;//單詞開始標記
int endIndex = 0;//單詞結束標記
//翻轉每個單詞的字母順序
while(endIndex <= length) {
if(endIndex == length || chars[endIndex] == ' ') {//遇到空格或到句末,翻轉單詞
reversed(chars, startIndex, endIndex - 1);
startIndex = endIndex + 1;
}
endIndex ++;
}
//翻轉整個句子的順序
reversed(chars, 0, length - 1);
return new String(chars);
}
private void reversed(char[] chars, int start, int end) {
while(start < end) {
char tmp = chars[start];
chars[start] = chars[end];
chars[end] = tmp;
start ++;
end --;
}
}
T45. 二叉樹的深度
題目描述
LL今天心情特別好,因爲他去買了一副撲克牌,發現裏面居然有2個大王,2個小王(一副牌原本是54張_)…他隨機從中抽出了5張牌,想測測自己的手氣,看看能不能抽到順子,如果抽到的話,他決定去買體育彩票,嘿嘿!!“紅心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是順子…LL不高興了,他想了想,決定大\小 王可以看成任何數字,並且A看作1,J爲11,Q爲12,K爲13。上面的5張牌就可以變成“1,2,3,4,5”(大小王分別看作2和4),“So Lucky!”。LL決定去買體育彩票啦。 現在,要求你使用這幅牌模擬上面的過程,然後告訴我們LL的運氣如何, 如果牌能組成順子就輸出true,否則就輸出false。爲了方便起見,你可以認爲大小王是0。
解題思路
先對數組排序,計算大小王(癩子)數量。然後計算剩下的數兩兩的差值減1即爲需要用癩子替代的張數。題那麼長都是廢話,別想太多正常找就行。
public boolean isContinuous(int[] numbers) {
if(numbers == null || numbers.length < 5) return false;
Arrays.sort(numbers);
int sum0 = 0;//大小王數量
for(int i = 0; i < numbers.length; i ++) {
if(numbers[i] == 0) {
sum0 ++;
}
}
for(int i = sum0; i < numbers.length - 1; i ++) {
if(numbers[i + 1] == numbers[i]) return false;//有相等的牌不可能爲順子
int interval = numbers[i + 1] - numbers[i] - 1;//這兩個數之間差了幾張牌,需要用大小王代替
if(interval > sum0) return false;//相差太大,大小王不夠
sum0 -= interval;
}
return true;
}
T46. 孩子們的遊戲(圓圈中最後剩下的數)
題目描述
每年六一兒童節,牛客都會準備一些小禮物去看望孤兒院的小朋友,今年亦是如此。HF作爲牛客的資深元老,自然也準備了一些小遊戲。其中,有個遊戲是這樣的:首先,讓小朋友們圍成一個大圈。然後,他隨機指定一個數m,讓編號爲0的小朋友開始報數。每次喊到m-1的那個小朋友要出列唱首歌,然後可以在禮品箱中任意的挑選禮物,並且不再回到圈中,從他的下一個小朋友開始,繼續0…m-1報數…這樣下去…直到剩下最後一個小朋友,可以不用表演,並且拿到牛客名貴的“名偵探柯南”典藏版(名額有限哦!!_)。請你試着想下,哪個小朋友會得到這份禮品呢?(注:小朋友的編號是從0到n-1)
解題思路
又是一堆廢話,丟手絹遊戲。。。
約瑟夫環,公式:
n = 1: f(n, m) = 0
n > 1: f(n, m) = [f(n - 1, m) + m] % n
public int LastRemaining_Solution(int n, int m) {//約瑟夫環
//公式:f(n, m) = 0 (n = 1)
//f(n, m) = [f(n - 1, m) + m] % n (n > 1)
if(n == 0) return -1;
if(n == 1) return 0;
return (LastRemaining_Solution(n - 1, m) + m) % n;
}
T47. 求1+2+3+…+n
題目描述
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。
解題思路
遞歸相加。此題關鍵在於如何跳出遞歸,基本方向是採用邏輯與或的方式來計算,與的時候通過n>0來短路,這樣在n=0的時候不需要計算遞歸的值,或的時候通過n==0來短路,在n=0的時候可以短路邏輯或運算。
public int Sum_Solution(int n) {
int sum = n;
//boolean b = (n > 0) && (sum += Sum_Solution(n - 1)) > 0;
boolean b = (n == 0) || (sum += Sum_Solution(n - 1)) > 0;
return sum;
}
T48. 不用加減乘除做加法
題目描述
寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、*、/四則運算符號。
解題思路
位運算。a ^ b 表示沒有考慮進位的情況下兩數的和,(a & b) << 1 就是進位。
public int Add(int num1, int num2) {
while(num2 != 0) {
int tmp = num1 ^ num2;
num2 = (num1 & num2) << 1;
num1 = tmp;
}
return num1;
}
T49. 把字符串轉換成整數
題目描述
將一個字符串轉換成一個整數(實現Integer.valueOf(string)的功能,但是string不符合數字要求時返回0),要求不能使用字符串轉換整數的庫函數。 數值爲0或者字符串不是一個合法的數值則返回0。
輸入描述:
輸入一個字符串,包括數字字母符號,可以爲空
輸出描述:
如果是合法的數值表達則返回該數字,否則返回0
解題思路
正常轉換即可。
注意:正負號的存在,字符型代表的整形在轉換過程中的計算。
public int StrToInt(String str) {
if(str.length() == 0) return 0;
char[] chars = str.toCharArray();
boolean isNegative = chars[0] == '-';//判斷是否有負號
int result = 0;
for(int i = 0; i < chars.length; i ++) {
if(i == 0 && (chars[i] == '+' || chars[i] == '-')) continue;//跳過正負號
if(chars[i] < '0' || chars[i] > '9') return 0;
result = result * 10 + (chars[i] - '0');
}
return isNegative ? -result : result;//三元
}
項目地址:https://github.com/JohnnyJYWu/offer-Java
上一篇:算法 | 一週刷完《劍指Offer》 Day3:第27~37題
下一篇:算法 | 一週刷完《劍指Offer》 Day5:第50~58題
希望這篇文章對你有幫助~