文章目錄
歡迎關注個人數據結構專欄哈
劍指offer(1-10題)詳解
劍指offer(11-25題)詳解
劍指offer(26-33題)詳解
劍指offer(34-40題)詳解
劍指offer(41-50題)詳解
劍指offer(51-59題)詳解
劍指offer(60-67題)詳解
微信公衆號:bigsai
聲明:大部分題基本未參考題解,基本爲個人想法,如果由效率太低的或者錯誤還請指正!
41 和爲S的連續正數序列
題目描述
小明很喜歡數學,有一天他在做數學作業時,要求計算出9~16的和,他馬上就寫出了正確答案是100。但是他並不滿足於此,他在想究竟有多少種連續的正數序列的和爲100(至少包括兩個數)。沒多久,他就得到另一組連續正數和爲100的序列:18,19,20,21,22。現在把問題交給你,你能不能也很快的找出所有和爲S的連續正數序列? Good Luck!
輸出描述:
輸出所有和爲S的連續正數序列。序列內按照從小至大的順序,序列間按照開始數字從小到大的順序
思路:
這題幾個要求:大於等於兩個序列,正整數,連續。至於對list長度返回的要求直接重寫個排序接口即可。
所以這題筆者大概想到2個思路
:
方法一:動態窮舉試探(較爲簡單)
這個窮舉並不是所謂的暴力遍歷,不從每個數都進行循環試起。如果從每個數都試起那麼整個進行的次數太多
。我們用一個類似拉鍊的動態滑動試探。我們都知道:連續相加和相等,如果數很小那麼就很長,如果數很大就很短。
- 所以我們用
left
和right
標記左右,用一個value記錄這個區間的和。 - 如果value大了那就左面往右試探一位,如果value小了那麼right就往右加變長一位。
- 重複上述操作。一直到中間兩位長度的截至,記錄所有滿足題意的條件。
法一的ac的代碼爲:
import java.util.*;
public class Solution {
public static ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum)
{
ArrayList<ArrayList<Integer> > list=new ArrayList<ArrayList<Integer>>();
int left=1,right=2,value=3;
while(left<=sum/2)
{
if(value==sum)
{
ArrayList<Integer>list1=new ArrayList<Integer>();
for(int i=left;i<=right;i++)
{
list1.add(i);
}
list.add(list1);
value-=left;left++;
}
else if(value>sum)
{
value-=left;left++;
}
else {
right++;value+=right;
}
}
list.sort(compare);
return list;
}
static Comparator<ArrayList<Integer>>compare=new Comparator<ArrayList<Integer>>() {
public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
// TODO Auto-generated method stub
return o2.size()-o1.size();
}
};
}
方法二:數學轉化
既然是連續的數字之和。那麼請看下面的分析:
/**
* 分析 a + (a+1) --- + b =(a+b) * n/2 =sum 其中n是a-b的個數
* (a+b) * (b-a+1)=sum * 2
* 令a+b=m a=a b=m-a
* m*(m-2a+1)=sum * 2
* 利用求sum * 2 的因數遍歷m 求出a,b若a大於0、且不爲小數、a!=b即爲一個序列
*/
大致分析是這樣的:
- 首先連續相加是可以轉化成乘法的(有時間可以瞭解唯一分解定理)
- 然後比如 sum * 2 =a * b 可能其中a是平均數,也可能b是平均數。但是你需要考慮所有和都大於0. 且爲整數。
- 比如 求和爲15.右
[1, 2, 3, 4, 5], [4, 5, 6], [7, 8]
三組數據。而求和分別爲((1+5)/2)*5=15
=>6*5=30
,((4+6)/2)*3=15
=>10*3=30
,((7+8)/2)*2=15
=>15*2=30
.其實你就是需要將所有a*b=30
所有情況列舉,然後看看能否復原成功。復原公式在上面的。並且其中a,b需要換位考慮能否成功的。當然求因數的一些技巧不需要詳細講述了,只需要遍歷根號(n)+1
即可。
具體代碼爲:
import java.util.*;
public class Solution {
public static ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
int value=sum*2;
ArrayList<ArrayList<Integer> > list=new ArrayList<ArrayList<Integer>>();
int a=0,b=0;
for(int i=1;i*i<value+1;i++)
{
if(value%i==0)
{
a=(i+1-value/i)/2;b=i-a;
if(a>0&&i-a*2+1==value/i&&b>a)
{
ArrayList<Integer>list2=new ArrayList<Integer>();
for(int j=a;j<=b;j++)
{
list2.add(j);
}
list.add(list2);
}
a=(value/i+1-i)/2;b=value/i-a;
if(a>0&&value/i-a*2+1==i&&b>a)
{
ArrayList<Integer>list2=new ArrayList<Integer>();
for(int j=a;j<=b;j++)
{
list2.add(j);
}
list.add(list2);
}
}
}
list.sort(compare);
return list;
}
static Comparator<ArrayList<Integer>>compare=new Comparator<ArrayList<Integer>>() {
public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
// TODO Auto-generated method stub
return o2.size()-o1.size();
}
};
}
42 和爲S的兩個數字
題目描述
輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,如果有多對數字的和等於S,輸出兩個數的乘積最小的。
輸出描述
對應每個測試案例,輸出兩個數,小的先輸出。
思路:
這題和上一題的方案一思想差不多。用個左右標記位置。從兩邊向中間逼近。兩個數相加如果大於目標,那麼最右側位置right往左移動一下,如果小於目標,左側的left向右移動增加,當出現第一個滿足條件的即可停止。因爲要求乘積最小嘛
。
實現代碼爲:
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
int small=0,big=0;//小大兩個數
int l=0, r=array.length-1;
while(l<r)
{
if(array[l]+array[r]==sum)
{
small=array[l];big=array[r];
break;
}
else if (array[l]+array[r]<sum) {
l++;
}
else if (array[l]+array[r]>sum) {
r--;
}
}
ArrayList<Integer>list=new ArrayList<Integer>();
if(small==big)return list;
list.add(small);
list.add(big);
return list;
}
}
43 左旋轉字符串
題目描述
彙編語言中有一種移位指令叫做循環左移(ROL),現在有個簡單的任務,就是用字符串模擬這個指令的運算結果。對於一個給定的字符序列S,請你把其循環左移K位後的序列輸出。例如,字符序列S=”abcXYZdef”,要求輸出循環左移3位後的結果,即“XYZdefabc”。是不是很簡單?OK,搞定它!
思路:
可以看似循環鏈表類似的問題。但是直接求就好了。因爲只旋轉一次,相當於把前面一部分挪到後面就可以了。要考慮旋轉的長度是否大於整個字符串的長度。
實現代碼:
public class Solution {
public String LeftRotateString(String str,int n) {
if(str.equals(""))return str;
n=n%str.length();
return str.substring(n)+str.substring(0,n);
}
}
44 翻轉單詞順序列
題目描述
牛客最近來了一個新員工Fish,每天早晨總是會拿着一本英文雜誌,寫些句子在本子上。同事Cat對Fish寫的內容頗感興趣,有一天他向Fish借來翻看,但卻讀不懂它的意思。例如,“student. a am I”。後來才意識到,這傢伙原來把句子單詞的順序翻轉了,正確的句子應該是“I am a student.”。Cat對一一的翻轉這些單詞順序可不在行,你能幫助他麼?
思路:
經典字符串處理題。字符串問題就是有很多坑點、邊界問題、特殊情況等等。要考慮空串、一個單詞等等情況。當然,具體處理就是字符一個個讀取,遇到空格
的時候特殊處理一下。處理方式可能不唯一。也可以考慮字符串分割等等。
實現代碼爲:
public class Solution {
public static String ReverseSentence(String str) {
String value="";
StringBuilder sBuilder=new StringBuilder();
for(int i=0;i<str.length();i++)
{
if(str.charAt(i)!=' ')
{
sBuilder.append(str.charAt(i));
}
else {
value=sBuilder.toString()+" "+value;
sBuilder=new StringBuilder();
}
}
if(sBuilder.length()>0)
{
value=sBuilder.toString()+" " +value;
value=value.substring(0,value.length()-1);
}
return value;
}
}
45 撲克牌順序
題目描述
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。
思路:
首先要理清楚題意。就是給你五張牌讓你判斷是不是順子,然而這五張可能有若干張鬼(0)
可以隨意的陪。這也有點小貪心的思想的。我們的處理思路事這樣的:
- 先給五張牌進行排序。然後前面用個數值記錄有幾個鬼。
- 然後從非鬼開始判斷是否連續,如果和前一個不連續
如果有鬼
用鬼前一個數往後補一張一直到連續,如果沒鬼了還不連續那就說明那是真的完成不了。 - 如果最後未能遍歷所有牌。或者有相同牌(非鬼)那麼一定不是順子。
實現代碼爲:
import java.util.Arrays;
public class Solution {
public static boolean isContinuous(int [] numbers) {
if(numbers.length<5)return false;
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers));
int wang=0;
int pre=0;
for(int i=0;i<5;i++)
{
if(numbers[i]==0)wang++;
else {
if(pre==0) {pre=numbers[i];}//第一個數
else if(numbers[i]==pre) {return false;}//相同牌
else if(numbers[i]==pre+1) {pre++;}
else {
while(numbers[i]>pre+1)
{
if(wang>0){wang--;pre++;}
else if(wang<=0) {return false;}
}
pre++;
}
}
}
return true;
}
}
46 孩子們的遊戲
題目描述
每年六一兒童節,牛客都會準備一些小禮物去看望孤兒院的小朋友,今年亦是如此。HF作爲牛客的資深元老,自然也準備了一些小遊戲。其中,有個遊戲是這樣的:首先,讓小朋友們圍成一個大圈。然後,他隨機指定一個數m,讓編號爲0的小朋友開始報數。每次喊到m-1的那個小朋友要出列唱首歌,然後可以在禮品箱中任意的挑選禮物,並且不再回到圈中,從他的下一個小朋友開始,繼續0…m-1報數…這樣下去…直到剩下最後一個小朋友,可以不用表演,並且拿到牛客名貴的“名偵探柯南”典藏版(名額有限哦!!_)。請你試着想下,哪個小朋友會得到這份禮品呢?(注:小朋友的編號是從0到n-1)
如果沒有小朋友,請返回-1
思路:
經典約瑟夫環問題。可用鏈表進行模擬,也可用List藉助數學方法一個個移出效率會高一些。筆者這裏用了個鏈表過了。後面用數學模擬鏈表的方法效率更高,大過年的先不寫了,後面會補的!
實現代碼爲:
public class Solution {
static class node
{
int val;
public node(int value) {
this.val=value;
}
node next;
}
public static int LastRemaining_Solution(int n, int m) {
if(n<=0||m<2)return -1;
node head=new node(0);
node team=head;
for(int i=1;i<n;i++)
{
team.next=new node(i);
team=team.next;
}
team.next=head;
int index=0;
while (head.next!=head) {
if(index==m-2)
{
head.next=head.next.next;
index=0;
}
else {
index++;
}
head=head.next;
}
return head.val;
}
}
至於筆者最初寫的效率並不高。至於數學模擬,筆者最初記得還是分類討論的:是否越界之類。而可以直接加上一段size長度求餘相當於接上那麼一段!
47 求1+2+。。。+n
題目描述
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。
思路:
這個真的沒想到,看了下評論區的方法真的是恍然大悟。雖然我們不能用邏輯語句實現我們的邏輯,但是我們可以用&&
之類的運算符幫助我們實現邏輯。a&&b
這個邏輯當a是false的時候後面就不會執行了。這就是邏輯與的短路特性。我們調用遞歸或者其他方法藉助邏輯與的短路特性就可以實現停止了。
實現代碼爲:
public class Solution {
public int Sum_Solution(int n) {
boolean jud= (n>0)&&((n+= Sum_Solution(n-1))>0);
return n;
}
}
參考評論區:
對於評論區還有一些其他方法也挺精妙,但是可能自己很難有那麼高認識,先了解吧。
48 不用加減乘除做加法★
題目描述
寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、*、/四則運算符號。
思路:
這個我以前就知道跟二進制有關,知道有這個東西不過沒仔細研究。我覺得這題大家可以自己獨立研究一下。能夠加深對二進制和位運算的印象和認識。
當然,做這題前,建議稍微瞭解下四種常用的位運算與或非 異或
。
對於二進制的運算:0+0=0,0+1=1,1+1=0(進位)
對於加法的一個二進制運算。如果不進位那麼就是非常容易的。這時候相同位都爲0則爲0,0和1則爲1.滿足這種運算的異或(不相同取1,相同取0)
和或(有一個1則爲1)
都能滿足.
但事實那像這麼容易?肯定有進位的運算啊!對於進位的兩數相加,這種核心思想爲:
- 用兩個數,一個正常m相加(不考慮進位的)。用
異或a^b
就是滿足這種要求,先不考慮進位(如果沒進位那麼就是最終結果)。 - 另一個專門考慮進位的n。兩個1需要進位。所以我們用
a&b與
記錄需要進位的。但是還有個問題,進位的要往上面進位,所以就變成這個需要進位的數左移一位。 - 然後就變成m+n重新開始上面直到不需要進位的(會停止的,不可能一直進位)。
實現代碼爲:
public class Solution {
public int Add(int num1,int num2) {
/*
* 5+3 5^3 5&3
* 0101 0110 0001
* 0011
*/
int a=num1^num2;
int b=num1&num2;
b=b<<1;
if(b==0)return a;
else {
return Add(a, b);
}
}
}
49 把字符串轉換成整數
題目描述
將一個字符串轉換成一個整數,要求不能使用字符串轉換整數的庫函數。 數值爲0或者字符串不是一個合法的數值則返回0
輸入描述:
輸入一個字符串,包括數字字母符號,可以爲空
輸出描述:
如果是合法的數值表達則返回該數字,否則返回0
示例1
輸入
+2147483647
1a33
輸出
2147483647
0
思路:
這題是個麻煩題,主要是有幾個越界的字符串需要處理。你又不能使用api,建議你使用long
型轉換成功判斷是否越界可能更方便些。但是筆者挑戰了下自己用int型硬剛過的。
你要考慮正負(整數最小和負數最大),需要考慮是否爲非法字符串。數值是否操作越界(尤其注重int類型最大最小附近)。
我的處理思路這樣的:記錄正負,每次都進行加操作,記錄操作前數值,如果操作後符號變化那就越界直接返回0。否則進行到最後返回。
實現代碼爲:
public class Solution {
public static int StrToInt(String str) {
if(str.equals(""))return 0;
int check = 0;//判斷首位是不是符號
int zhengfu = 1;//正負號用1和-1表示
if (str.charAt(0) == '+' || str.charAt(0) == '-')// 符號位判斷下
{
check=1;
zhengfu=str.charAt(0)=='+'?1:-1;
}
int index = check == 0 ? 0 : 1;// 初始位置
int value = 0;
int team=zhengfu;
for (; index < str.length(); index++) {
if (str.charAt(index) >= '0' && str.charAt(index) <= '9') {
value = value * 10 + (str.charAt(index) - '0')*zhengfu;
if(value/team<0)return 0;
team=value;
//System.out.println(value+" "+zhengfu);
} else {
return 0;
}
}
return value;
}
}
50 數組中重複的數字
題目描述
在一個長度爲n的數組裏的所有數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出數組中任意一個重複的數字。 例如,如果輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2。
思路:
因爲數組中的數範圍已經確定了,也不大,不需要用hashmap。直接用一個數組即可。遍歷一個加一個,這個位置的數爲2返回停止。
實現代碼爲:
public class Solution {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 這裏要特別注意~返回任意重複的一個,賦值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
int count[]=new int[length+1];
for(int i=0;i<length;i++)
{
if(++count[numbers[i]]>1)
{
duplication[0]=numbers[i];
return true;
}
}
return false;
}
}
最後歡迎大家關注公衆號:bigsai
長期交流、分享!