劍指offer6-面試中的各項能力

題目-數字在排序數組中出現的次數 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

 
 
class Solution {
    public int search(int[] array, int k) {
        /*int ret=0;
        for(int i=0;i<nums.length;i++){
            if(nums[i]==target){
                ret++;
            }
        }
        return ret;*/
        if(array.length == 0)
            return 0;
        int left=0;
        int right=array.length-1;
        int index=0;
        //二分查找,找到left,即找到第一個k
        while(left<right){
            int mid = (left+right)/2;
            if(array[mid] >= k){
                right = mid;
            }
            else if(array[mid]<k){
                left = mid+1;
            }
            
        }        
        //遍歷查找k
        int count=0;
        while(left<array.length && array[left++]==k)
            count++;
        return count;
    }
}


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(); } }
相關文章
相關標籤/搜索