題目-數字在排序數組中出現的次數 java
統計一個數字在排序數組中出現的次數。
如Input:
array=1,2,3,3,3,3,4,6
k=3
Output:
4
思路算法
1.因爲輸入的數組是排序的,那麼二分查找算法很適用這個場景。二分查找很容易找到一個3,因爲3可能出現不少次,所以咱們能夠在3的左右兩邊按順序掃描,分別找到第一個3和最後一個3.數組
由於要查找的數字在長度爲n的數組中有可能出現O(n)次,因此順序掃描的時間複雜度爲O(n)函數
2.優化的解法是:用二分查找算法直接找到第一個k和最後一個k優化
思路就是:(以找到第一個k爲例)二分查找老是拿數組中間的數字和k做比較,若是中間的數字比k大,那麼k只有可能出如今數組的前半段,下一輪只要在數組的前半段查找就行了。反之。this
若是中間數字==k,那麼先判斷這個數字是否是第一個k。若是位於中間數字的前一個數不是k,那麼說明中間數就是第一個k;若是前一個數也是k,說明第一個k確定在數組的前半段,下一輪依然須要在數組的前半段查找。spa
這樣查找第一個k和最後一個k都是,使用二分查找法在數組中查找一個合乎要求的數字,時間複雜度都是O(logn)。指針
解法code
public class Solution {
//這種解法在leetcode給的例子裏,{1,2,3}&3,這個樣例不能輸出正確結果;當循環條件改成while(left<=right)時,則{1,1,2}&2樣例不能正確輸出結果 public int GetNumberOfK1(int [] array , int k) { if(array.length == 0) return 0; int left=0; int right=array.length-1; int index=0; //二分查找,找到靠中間的那個k while(left<right){ int mid = (left+right)/2; if(array[mid] == k){ index = mid; break; } else if(array[left]<array[mid]){ left = mid+1; } else{ right = mid-1; } } //遍歷查找k int count=0; //從找到的index,往前查找 for(int i=index; i>=0 && array[i]==k; i--) count++; //從找到的index,日後查找 for(int i=index+1; i<array.length && array[i]==k; i++) count++; return count; }
public int GetNumberOfK2(int [] array , int k) {
if(array==null)
return 0;
int first=GetFirstK(array, array.length, k, 0, array.length-1);
int last=GetLastK(array, array.length, k, 0, array.length-1);
int index=0;
if(first>-1 && last>-1){
index=last-first+1;
}
return index;
}
public int GetFirstK(int[] array, int length, int k, int left, int right){
if(left>right){
return -1;
}
int mid=(left+right)/2;
//int midData=array[mid];
if(array[mid]==k){
if((mid>0 && array[mid-1]!=k)|| mid==0){
return mid;
}
else{
right=mid-1;
}
}
else if(array[mid]>k){
right=mid-1;
}
else{
left=mid+1;
}
return GetFirstK(array, length, k, left, right);
}
public int GetLastK(int[] array, int length, int k, int left, int right){
if(left>right){
return -1;
}
int mid=(left+right)/2;
//int midData=array[mid];
if(array[mid]==k){
if((mid<length-1 && array[mid+1]!=k)|| mid==length-1){
return mid;
}
else{
left=mid+1;
}
}
else if(array[mid]>k){
right=mid-1;
}
else{
left=mid+1;
}
return GetLastK(array, length, k, left, right);
} }
題目-二叉樹的深度blog
輸入一棵二叉樹,求該樹的深度。從根結點到葉結點依次通過的結點(含根、葉結點)造成樹的一條路徑,最長路徑的長度爲樹的深度。
思路
(該樹的深度爲4,因爲從根結點到葉結點最長的路徑包含4個結點)
若是一棵樹只有一個結點,那麼深度爲1;
若是根結點只有左子樹沒有右子樹,那麼樹的深度應該是左子樹深度+1;
反之,深度應該是右子樹深度+1;
若是既有左子樹又有右子樹,那麼就是深度的較大值+1.
可使用遞歸來實現。
解法
/** public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { public int TreeDepth(TreeNode root) { if(root == null) return 0; int left = TreeDepth(root.left); int right = TreeDepth(root.right); return (left>right)?(left+1):(right+1); } }
題目-平衡二叉樹
輸入一棵二叉樹,判斷該二叉樹是不是平衡二叉樹。
思路
平衡二叉樹的概念是:若是某二叉樹中任意結點的左右子樹的深度相差不超過1。
1.最簡單的思路就是直接在遍歷樹的每一個結點的時候,調用上面的求樹深度的函數獲得結點的 左右子樹 的深度。若是某結點的左右子樹深度相差超過1,則不是平衡樹。
這種方式出現一個結點會被重複遍歷屢次的狀況,致使時間效率不高。
2.所以考慮每一個結點只遍歷一次的解法
若是用後序遍歷的方式遍歷二叉樹的每一個結點,在遍歷到一個結點以前,已經遍歷到它的左右子樹。
那麼只要在遍歷每一個結點的時候記下它的深度,就能夠一邊遍歷一邊判斷每一個結點是否是平衡的。
解法
public class Solution { private boolean isBalanced=true; public boolean IsBalanced_Solution(TreeNode root) { height(root); return isBalanced; } private int height(TreeNode root){ if(root==null || !isBalanced) return 0; int left = height(root.left); int right = height(root.right); //平衡樹中任意結點的左右子樹的深度相差不超過1 if(Math.abs(left-right)>1) isBalanced = false; return 1+Math.max(left, right); } }
題目-數組中只出現一次的數字
一個整型數組裏除了兩個數字以外,其餘的數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。
思路
1.咱們首先能夠考慮若是隻有一個數字出現了一次,那麼考慮異或的性質:任何一個數字異或它本身都等於0.即從頭至尾遍歷數組,最後的結果是隻出現一次的數字,成對出現兩次的數字所有在異或中抵消了。
2.那麼有了上面的思路。對於原題,咱們能夠試圖把原數組拆分紅兩個子數組,使每一個子數組包含一個只出現一次的數字,其餘數字都承兌出現兩次。
3.那麼如何這樣拆分數組呢?
因爲不相同的元素在位級表示中一定會有一位不一樣,那麼全部元素異或獲得的結果是 不存在重複的兩個元素異或的結果。也就是這個結果 的二進制表示中 至少有一位 爲1,咱們在結果數字中找到第一個爲1的位的位置,記爲n。
以第n位是否是1爲標準,把原數組中的數字分爲兩個子數組。第一個子數組中每一個數字的第n位都是1,而第二個子數組中每一個數字的第n位都是0.
diff &= -diff 獲得出 diff 最右側不爲 0 的位,也就是不存在重複的兩個元素在位級表示上最右側不一樣的那一位,利用這一位就能夠將兩個元素區分開來。
求相反數:取反+1.(-n=(~n)+1)
解法
//num1,num2分別爲長度爲1的數組。傳出參數 //將num1[0],num2[0]設置爲返回結果 public class Solution { public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { int diff=0; //相同的數字異或爲0,0與其餘數字異或獲得原數字 //所有異或運算獲得一個值,此時結果必定非0 for(int num:array){ diff^=num; } //獲得diff最右側不爲0的位,也就是不存在重複的兩個元素在位級表示上最右側不一樣的那一位,利用這一位能夠將兩個元素區分開 //即獲得該值二進制最低位1的位置index diff &= -diff; //按照最低位index是0/1把原數組分爲兩組 //每組進行連續異或運算獲得只出現一次的數字,由於相同的數會得0 for(int num:array){ if((num&diff)==0) num1[0]^=num; else num2[0]^=num; } } }
class Solution { public int[] singleNumbers(int[] nums) { int sum=0; for(int num:nums){ sum^=num; } //找從右向左數第幾位不一樣,也就是第index位 int index=0; while((sum&1) == 0){ index++; sum>>=1; } int[] ret=new int[2]; for(int num:nums){ if(((num>>index)&1) == 0){ ret[0]^=num; } else{ ret[1]^=num; } } return ret; } }
題目-和爲S的兩個數字
輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,若是有多對數字的和等於S,輸出兩個數的乘積最小的。
思路
使用兩個指針,一個指向數組頭,一個指向數組尾。
頭指針日後遍歷,尾指針向前遍歷。
不斷收攏兩個指針的間距,直到符合和爲S的要求。
若是多對,須要輸出乘積最小的,因爲是從頭尾開始查找,當前知足條件的頭指針位置必定是最小的。所以能獲得乘積最小。
解法
import java.util.ArrayList; public class Solution { public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) { ArrayList<Integer> ret = new ArrayList<Integer>(); if(array==null) return ret; int start=0,end=array.length-1; while(start<end){ int curSum=array[start]+array[end]; //若是和大於,則減少 if(curSum>sum){ end--; } //若是小於,則加 else if(curSum<sum){ start++; } else{ ret.add(array[start]); ret.add(array[end]); break; } } return ret; } }
題目-和爲S的連續正數序列
小明很喜歡數學,有一天他在作數學做業時,要求計算出9~16的和,他立刻就寫出了正確答案是100。可是他並不知足於此,他在想究竟有多少種連續的正數序列的和爲100(至少包括兩個數)。沒多久,他就獲得另外一組連續正數和爲100的序列:18,19,20,21,22。如今把問題交給你,你能不能也很快的找出全部和爲S的連續正數序列? Good Luck!
思路
借鑑上題的經驗,那咱們一樣也能夠以int設置兩個指針start,end,分別表示序列的最小值和最大值。
若是序列之和大於S,那麼就從序列中去除較小的數,就增大start的值;
若是和小於S,那麼就增長end,在序列中增長更多的數字。
若是和==S,那麼就是找到一個序列。首先把它添加進去,而後再增長end和start,把整個序列日後移動一位。再次進行比較和查找
解法
import java.util.ArrayList;
public class Solution { public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) { ArrayList<ArrayList<Integer>> ret = new ArrayList<>(); int start = 1,end = 2; int curSum = 3; while(end < sum){ //大於sum,則從序列中去掉較小的值。也就是當前的start增大 if(curSum > sum){ curSum=curSum-start; start++; } //小於sum,那麼須要讓序列包含更多的數字 else if(curSum < sum){ end++; curSum=curSum+end; } //找到一個序列 else{ ArrayList<Integer> list = new ArrayList<>(); //因爲是連續序列,把序列中的數字加到list中 for(int i=start; i<=end; i++){ list.add(i); } ret.add(list); curSum=curSum-start; start++; end++; curSum=curSum+end; } } return ret; } }
題目-左旋轉字符串
彙編語言中有一種移位指令叫作循環左移(ROL),如今有個簡單的任務,就是用字符串模擬這個指令的運算結果。對於一個給定的字符序列S,請你把其循環左移K位後的序列輸出。例如,字符序列S=」abcXYZdef」,要求輸出循環左移3位後的結果,即「XYZdefabc」。是否是很簡單?OK,搞定它!
思路
對於這個,咱們能夠列出字符串及其旋轉後的效果。那麼咱們能夠發現一些規律:
好比對於序列S="abcXYZdef",能夠將其分爲兩部分,前面的3個字符爲第一部分,後面的爲第二部分。
而後咱們分別反轉這兩部分,變成cba和fedZYX。
那麼再將整個字符串旋轉,獲得左移3位的效果,「XYZdefabc」
(注意在java中,爲了處理單個字符,咱們一般須要把String類型轉化爲char[] )
解法
public class Solution { public String LeftRotateString(String str,int n) { if(str.length() <= n){ return str; } char[] chars = str.toCharArray(); reverse(chars, 0, n-1); reverse(chars, n, str.length()-1); reverse(chars, 0, str.length()-1); return new String(chars); } private void reverse(char[] chars, int i, int j){ while(i<j){ swap(chars, i++, j--); } } private void swap(char[] chars, int i, int j){ char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; } }
題目-翻轉單詞順序列
牛客最近來了一個新員工Fish,天天早晨老是會拿着一本英文雜誌,寫些句子在本子上。同事Cat對Fish寫的內容頗感興趣,有一天他向Fish借來翻看,但卻讀不懂它的意思。例如,「student. a am I」。後來才意識到,這傢伙原來把句子單詞的順序翻轉了,正確的句子應該是「I am a student.」。Cat對一一的翻轉這些單詞順序可不在行,你能幫助他麼?
思路
整個題與上一題相似。
一樣是使用翻轉,那麼能夠把這個翻轉拆分爲兩步:
1.把每一個單詞都翻轉,使用start和end記錄當前這個單詞的位置,也就是須要翻轉的位置。
2.把整個句子翻轉
3.這個翻轉過程須要注意:只有一個單詞的狀況;有標點符號的狀況
解法
public class Solution { public String ReverseSentence(String str) { if(str==null){ return null; } int n = str.length(); char[] chars = str.toCharArray(); //記錄要反轉單詞的起始位置 int start=0, end=0; for(end=0; end<=n; end++){ if(end == n || chars[end] == ' '){ reverse(chars, start, end-1); start = end+1; } } reverse(chars, 0, n-1); return new String(chars); } private void reverse(char[] chars, int i, int j){ while(i<j){ swap(chars, i++, j--); } } private void swap(char[] chars, int i, int j){ char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; } }
題目-撲克牌順子
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.怎麼判斷數字是否是連續呢,直觀的方法就是將數組排序。
2.因爲有癩子的存在,那麼能夠用癩子(0)去填補數組中的空缺,若是排序後的數組不是連續的,也就是相鄰的數字間隔若干個數字,只要咱們有足夠多的癩子,那就能夠去填補這個空缺。
3.那麼總結起來過程就是:1)數組排序;2)統計數組中0的個數;3)最後統計排序後的數組中相鄰數字間的空缺;4)判斷0是否足夠去填補空缺。
4.須要注意的是,順子中不能出現對子,也就是不能出現重複的數字。
解法
import java.util.Arrays; public class Solution { public boolean isContinuous(int [] numbers) { if(numbers.length < 5){ return false; } Arrays.sort(numbers); //統計大小王,當成癩子 int cnt=0; for(int num:numbers){ if(num==0){ cnt++; } } //使用癩子補全順子 for(int i=cnt;i<numbers.length-1;i++){ //存在相同的數字,那麼就不是順子 if(numbers[i+1] == numbers[i]) return false; cnt-=numbers[i+1]-numbers[i]-1; } //若是能剩下癩子,或恰好夠補,那麼則是順子 return cnt>=0; } }
題目-孩子們的遊戲(圓圈中最後剩下的數)
每一年六一兒童節,牛客都會準備一些小禮物去看望孤兒院的小朋友,今年亦是如此。HF做爲牛客的資深元老,天然也準備了一些小遊戲。其中,有個遊戲是這樣的:首先,讓小朋友們圍成一個大圈。而後,他隨機指定一個數m,讓編號爲0的小朋友開始報數。每次喊到m-1的那個小朋友要出列唱首歌,而後能夠在禮品箱中任意的挑選禮物,而且再也不回到圈中,從他的下一個小朋友開始,繼續0...m-1報數....這樣下去....直到剩下最後一個小朋友,能夠不用表演,而且拿到牛客名貴的「名偵探柯南」典藏版(名額有限哦!!^_^)。請你試着想下,哪一個小朋友會獲得這份禮品呢?(注:小朋友的編號是從0到n-1) 若是沒有小朋友,請返回-1
思路
1.這是一個經典的約瑟夫環問題,那麼經過分析每次被刪除的數字的規律 來直接計算圓圈中剩下的數字。
最後獲得一個遞歸公式:
要獲得n個數字的序列中最後剩下的數,只須要獲得n-1個數字的序列中最後剩下的數字。以此類推。
2.模擬小朋友退出遊戲的過程,用LinkedList鏈表來表示圓圈。而後每次按照要求刪除結點,((removeIndex+m-1)%list.size();)
解法
//解法1
import java.util.LinkedList; public class Solution { public int LastRemaining_Solution(int n, int m) { if(n == 0) return -1; if(n == 1){ return 0; } int last=0; for(int i=2; i<=n; i++) last=(last+m)%i; return last; }
//解法2
import java.util.LinkedList; public class Solution { public int LastRemaining_Solution(int n, int m) { if(n<1 || m<1){ return -1; } LinkedList<Integer> list = new LinkedList<Integer>(); for(int i=0;i<n;i++){ list.add(i); } //當前刪除的結點下標 int removeIndex=0; while(list.size()>1){ //下一個要刪除的結點的下標 removeIndex=(removeIndex+m-1)%list.size(); list.remove(removeIndex); } return list.getFirst(); } }