劍指offer5-優化時間和空間效率

題目-數組中出現次數超過一半的數字 java

數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度爲9的數組{1,2,3,2,2,2,5,4,2}。因爲數字2在數組中出現了5次,超過數組長度的一半,所以輸出2。若是不存在則輸出0。 

思路算法

1.數組中有一個數字出現的次數超過數組長度的一半==它出現的次數比其餘全部數字出現次數的和還要多;數組

所以咱們考慮一種思路,定義一個count。若是數字相同,則count++;若是數字不一樣,則count--;函數

這樣的話,最後存儲的序列元素m,就是這個序列中最多的元素。測試

2.上面這種思路其實就是Boyer-Moore Majority Vote Algorithm(摩爾投票算法),一種在線性時間O(n)和空間複雜度的狀況下,在一個元素序列中查找包含最多的元素。屬於流算法(streaming algorithm)優化

即從頭至尾遍歷數組,遇到兩個不同的數就把兩個數同時去掉,去掉的數可能都不是m,也可能一個是m。可是由於m出現的次數大於總長度的一半,因此刪完了最後剩下的數是m。this

本題是摩爾投票算法最簡單的形式。spa

3.本題須要注意的問題是:該序列中不必定存在長度大於數組一半的數,也就是以前存儲的序列中最多元素m,長度不必定符合要求。因爲以前已經找出了這個數字,那麼就再次驗證,判斷該數字的出現次數是否>數組一半的長度。指針

解法code

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        int m = array[0];
        int count=0;//初始化狀態下計數器爲0
//算法依次掃描序列中的元素
for(int i=0;i<array.length;i++){
       //處理元素x時,若是計數器爲0,將x賦值給m。
       if(count==0){ m=array[i]; count++; } else if(array[i]==m){ count++; } else count--; } //若是不存在長度大於數組一半的數,則輸出0 //第二次遍歷 count = 0; for(int v:array){ if(v == m) count++; } if(count<=array.length/2){ return 0; } return m; } }

 

題目-最小的K個數

輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。

思路

1.本題最簡單的思路就是把輸入的n個整數排序,排序後位於數列最前面的k個數就是所求。此時時間複雜度是O(nlogn)

2.借用快速排序。快排的partition()方法,會返回一個整數 j 使得 a[l .. j-1]小於等於 a[j],且a[j+1,.., h]大於等於a[j],此時 a[j] 就是數組的第 j 大元素。能夠利用這個特性找到數組的第K個元素。

當且僅當容許修改數組元素時纔可使用。複雜度是O(N)+O(1)

3.快排的基本思想:經過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另外一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。

4.若是不容許修改數組元素&處理海量數據的場景。那麼採用維護大小爲K的最小堆來實現。複雜度:O(NlogK)+O(K)

具體:在添加一個元素以後,若是大頂堆的大小大於K,那麼須要將大頂堆的堆頂元素去除。

解法

import java.util.ArrayList;
 
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> ret = new ArrayList<>();
        if(k > input.length || k <= 0)
            return ret;
        findKthSmallest(input, k-1);
     //findKthSmallest會改變數組,使得前k個數都是最小的k個數
for(int i=0; i<k ;i++) ret.add(input[i]); return ret; } public void findKthSmallest(int[] input, int k){ int low=0, high=input.length-1; while(low < high){ //找尋基準數據的正確索引 int j=partition(input, low, high); if(j==k) break; //進行迭代對index以前和以後的數組進行相同的操做使整個數組有序 if(j>k) high=j-1; else low=j+1; } } //分區操做,結束該分區後,基準數就放在數列的中間位置 private int partition(int[] input, int low, int high){ //基準數據 int p = input[low]; int i=low, j=high+1; while(true){ //從後往前找,找到比p小的第一個數 while(i!=high && input[++i]<p); //從前日後找,找到比p大的第一個數 while(j!=low && input[--j]>p); //直到i>=j,第一回結束。此時p以前的數都小於p,p以後的數都大於p if(i >= j) break; swap(input, i, j); } swap(input, low, j); return j; } private void swap(int[] input, int i, int j){ int t = input[i]; input[i] = input[j]; input[j] = t; } }

 

題目-連續子數組的最大和

HZ偶爾會拿些專業問題來忽悠那些非計算機專業的同窗。今天測試組開完會後,他又發話了:在古老的一維模式識別中,經常須要計算連續子向量的最大和,當向量全爲正數的時候,問題很好解決。可是,若是向量中包含負數,是否應該包含某個負數,並指望旁邊的正數會彌補它呢?例如:{6,-3,-2,7,-15,1,2,2},連續子向量的最大和爲8(從第0個開始,到第3個爲止)。給一個數組,返回它的最大連續子序列的和,你會不會被他忽悠住?(子向量的長度至少是1)

思路 

1.分析數組的規律。當從頭至尾累加數組的數字,初始化和爲0。如對於數組{6,-3,-2,7,-15,1,2,2},首先加上第一個數字6,此時和爲6;再加上-3,-2,7和爲8;再加上-15,此時和爲-7,小於0,若是用小於0的數字去加後面的數,還不如拋棄前面這段,直接從1開始。再設置一個greatestSum用來記錄當前最大子數組的和。

2.32位的int,正數的範圍是(0,0x7FFFFFFF),負數(0x80000000,0xFFFFFFFF)。因此設置int最小值爲0x80000000

解法

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        boolean invalidInput = false;
        if((array.length<=0) || (array==null) ){
            invalidInput = true;
            return 0;
        }
        int curSum=0;
        //32位的int,正數的範圍是(0,0x7FFFFFFF),負數(0x80000000,0xFFFFFFFF)
        int greatestSum=0x80000000;
        for(int i=0; i<array.length; i++){
            if(curSum<=0){
                curSum=array[i];
            }
            else
                curSum=curSum+array[i];
            if(curSum>greatestSum)
                greatestSum=curSum;
        }
        return greatestSum;
    }
}

 

題目-整數中1出現的次數(從1到n整數中1出現的次數)  

求出1~13的整數中1出現的次數,並算出100~1300的整數中1出現的次數?爲此他特別數了一下1~13中包含1的數字有一、101112、13所以共出現6次,可是對於後面問題他就沒轍了。ACMer但願大家幫幫他,並把問題更加廣泛化,能夠很快的求出任意非負整數區間中1出現的次數(從1 到 n 中1出現的次數)。 

思路

1.最直觀的方式是:累加,經過每次對10求餘判斷,整數的個位數字是否爲1.若是這個數字大於10,那麼再次除以10.

這種方式,若是輸入n,n爲O(logn)位,須要判斷每一位是否是1,那麼時間複雜度是O(n*logn)

解法

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        if(n<0)
            return 0;
        int count=0;
        for(int i=1;i<=n;i++){
            count+=numberOf1(i);
        }
        return count;
    }
     
    private static int numberOf1(int n){
        int count=0;
        while(n>0){
            if(n%10 == 1){
                ++count;
            }
            n=n/10;
        }
        return count;
    }
}

 

題目-把數組排成最小的數 

輸入一個正整數數組,把數組裏全部數字拼接起來排成一個數,打印能拼接出的全部數字中最小的一個。例如輸入數組{332321},則打印出這三個數字能排成的最小數字爲321323。 

思路

1.這個題目標就是找到一個排序規則,數組根據這個規則排序以後能排成一個最小的數字。

要肯定排序規則,就是比較兩個數字中,哪一個應該排在前面。即哪一個排在前面造成的數字會更小。

2.解決大數問題首要的方式就是:數字轉化成字符串,nums[i]=numbers[i]+"";比較的時候按照字符串大小的比較規則就能夠

3.數組的排序函數

解法

import java.util.Comparator;
import java.util.Arrays;
 
public class Solution {
    public String PrintMinNumber(int [] numbers) {
        int len=numbers.length;
        if(numbers == null || len == 0)
            return "";
        //數字轉成字符串
        String[] nums = new String[len];
        for(int i=0; i<len; i++){
            nums[i]=numbers[i]+"";
        }
        //進行排序,來實現獲得更小的數字
        Arrays.sort(nums, new Comparator<String>(){
            public int compare(String str1, String str2){
                String c1 = str1+str2;
                String c2 = str2+str1;
                return c1.compareTo(c2);
            }
        });
        String ret="";
        for(int i=0;i<len;i++){
            ret=ret+nums[i];
        }
        return ret;
    }
}

  

題目-醜數 

把只包含質因子二、3和5的數稱做醜數(Ugly Number)。例如六、8都是醜數,但14不是,由於它包含質因子7。 習慣上咱們把1當作是第一個醜數。求按從小到大的順序的第N個醜數。 

思路

1.因子的概念:一個數 m 是另外一個數 n 的因子,是指 n 能被 m 整除,也就是n%m==0。

2.最直觀的方式就是:逐個判斷每一個整數是否是醜數。可是這種方式的時間效率很低

3.那麼2中提到的方式,存在的問題是無論一個數是否爲醜數,都要計算。那就爲了優化這種方式,建立數組僅保存已經找到的醜數。以空間換時間。

因爲醜數的定義是:另外一個醜數乘以二、3或5的結果。

那麼咱們能夠建立一個數組,用來保存數組裏已有醜數,再將數組裏的數乘以二、3或5。可是須要作的是把找到的醜數進行排序。

若是當前數組中最大的醜數是M,下一個要生成的醜數必定是數組中某一個醜數乘以二、3或5的結果。因此首先須要考慮的是把已有的每一個醜數乘以2,咱們只獲取第一個大於M的結果。

但其實並非須要把每一個醜數都分別乘以二、3或5。因爲現有數組是有序存放的,那麼對乘以2而言,確定存在一個醜數T2,排在它以前的每一個醜數乘以2的結果 都會 小於 已有最大的醜數。而排在它以後的每一個醜數乘以2的結果 都會 太大。那麼咱們只須要記下這個醜數的位置,每次生成新的醜數時,就去更新T2。對乘以3和5而言,也存在一樣的T3和T5.

4.能夠當作3個數組的合併,A:{1*2,2*2,3*2,4*2,5*2,6*2,8*2,10*2......};B:{1*3,2*3,3*3,4*3,5*3,6*3,8*3,10*3......};C:{1*5,2*5,3*5,4*5,5*5,6*5,8*5,10*5......}

若是下一個數,是某個數組中的,那麼就把對應數組的指針日後移一位,

 

須要注意的是:若是這個數在多個數組中出現,那麼這多個數組中每一個數組的指針都須要日後移一位

解法

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index <= 0)
            return 0;
        int[] uglyArray = new int[index];
        uglyArray[0]=1;
        int multiply2 = 0;
        int multiply3 = 0;
        int multiply5 = 0;
        //按照大小生成醜數,這裏的全部數都是醜數
        for(int i=1;i<index;i++){
            int min = min(uglyArray[multiply2]*2, uglyArray[multiply3]*3, uglyArray[multiply5]*5);
            uglyArray[i]=min;
            while(uglyArray[multiply2]*2 == uglyArray[i])
                multiply2++;
            while(uglyArray[multiply3]*3 == uglyArray[i])
                multiply3++;
            while(uglyArray[multiply5]*5 == uglyArray[i])
                multiply5++;
        }
        return uglyArray[index-1];
    }
    public int min(int num1, int num2, int num3){
        int min = (num1<num2)?num1:num2;
        return min<num3?min:num3;
    }
}

 

題目-第一個只出現一次的字符

在一個字符串(0<=字符串長度<=10000,所有由字母組成)中找到第一個只出現一次的字符,並返回它的位置, 若是沒有則返回 -1(須要區分大小寫). 

如輸入「abaccdeff」,輸出「b」

思路

1.最直觀的方式就是使用HashMap對每一個字符在字符串中出現的次數進行統計,可是要統計的字符範圍有限,所以可使用整型數組代替HashMap。

須要從頭至尾掃描字符串兩次,第一次掃描字符串時,每掃描到一個字符就在數組的對應項中把次數加1;

第二次掃描,每掃描到一個字符就能獲得該字符的出現次數。那麼第一個只出現一次的字符就是符合要求的輸出。

2.優化的方式是:考慮到只須要找到只出現一次的字符,那麼須要統計的次數信息只有0,1或更大,使用兩個bit位就能夠存儲信息。

BitSet bs2 = new BitSet(256);

使用bs2.get(char )/bs2.set(char )來進行設置和提取操做

解法

public class Solution {
    public int FirstNotRepeatingChar(String str) {
        if(str == null){
            return -1; 
        }
        int[] cnts = new int[256];
        for(int i=0;i<str.length();i++){
            cnts[str.charAt(i)]++;
        }
        for(int i=0;i<str.length();i++){
            if(cnts[str.charAt(i)]==1)
                return i;
        }
        return -1;
    }
}
class Solution {
    public char firstUniqChar(String s) {
        char ret=' ';
        Map<Character,Integer> map=new HashMap<Character,Integer>();
        for(int i=0;i<s.length();i++){
            //獲取當前元素
            char temp=s.charAt(i);
            if(!map.containsKey(temp)){
                map.put(temp,1);
            }
            else{
                map.put(temp,2);
            }
        }
        for(int i=0;i<s.length();i++){
            if(map.get(s.charAt(i))==1){
                return s.charAt(i);
            }
        }
        return ret;
    }
}

 hashmap的使用方法:

創建hashmap:HashMap<Character,Integer> map=new HashMap<>();

增:map.put('c',4);

刪:map.remove('c');

改:map.put('c',6);

查:map.containsKey('d');  & map.containsValue(4);

根據key找value:map.get(key);

遍歷:1.使用keySet(),當只須要map中的鍵/值:Set<Character> keys=map.keySet();//返回key的集合  for(char key:keys){int value=map.get(key);//返回key對應的value值}

for(Integer value:map.values()){ int x=value;//獲得全部的value值}

2.使用entryKey():Set<Entry<Character,Integer>> entrySet=map.entrySet();//返回全部鍵值對的集合   

for(Entry<Character,Integer> entry:entrySet){char key=entry.getKey(); int value=entry.getValue;}

 

題目-數組中的逆序對

在數組中的兩個數字,若是前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數P。並將P對1000000007取模的結果輸出。 即輸出P%1000000007

思路

舉例統計數組{7,5,6,4}中逆序對的過程:逆序對爲(7,5),(7,6),(7,4),(5,4),(6,4)。

合併兩個子數組並統計逆序對的過程:

那麼總體思路就是:

1.先把數組分割成兩個子數組,統計出子數組內部的逆序對的數目,再統計出兩個相鄰子數組之間的逆序對的數目。

2.在統計逆序對的過程當中,還須要對數組進行排序。(由於若是是排序後,那麼若是左數組的第一個數大於右數組的第一個數,那麼左數組中每一個數與右數組的每一個數確定都能構成逆序對)

3.那麼這個過程其實就是歸併排序。

解法

public class Solution {
    private long reversePair = 0;
    public int InversePairs(int [] array) {
        int len = array.length;
        //數組爲null/長度爲0
        if(array == null || len == 0){
            return 0;
        }
        int[] p = new int[array.length];
        //排序
        mergeSort(array, 0, len-1, p);
        return (int)(reversePair%1000000007);
    }
    public void mergeSort(int [] array, int first, int last, int temp[]){
        if(first<last){
            int mid = (first+last)/2;
            //左邊有序
            mergeSort(array, first, mid, temp);
            //右邊有序
            mergeSort(array, mid+1, last, temp);
            //將左右兩個有序數列合併    
            mergeArray(array, first, mid, last, temp);
        }
    }
    //將兩個有序數列array[first,...,mid]和array[mid,...,last]合併
    public void mergeArray(int[] array, int first, int mid, int last, int[] temp){
        int first1 = first, end1 = mid;
        int first2 = mid+1, end2 = last;
        int p = 0;
        while(first1 <= end1 && first2 <= end2){
            //左數組大於右數組
            if(array[first1]>array[first2]){
                temp[p++] = array[first2++];
                //因爲當前數組都是有序數組,若是當前左數組元素大於右數組元素array[first2]
                //那麼就會有當前左數組後面的元素,都大於array[first2]
                reversePair+=mid-first1+1;
            }
            //若是左數組的數字 小於或等於 右數組的數字,不構成逆序對
            //每一次比較時,都把較大的數字從後往前複製到一個輔助數組中,確保輔助數組中的數組增序
            //把較大的數字複製到數組以後,把對應的指針向前移動一位,進行下一輪的比較
            else{
                temp[p++]=array[first1++];
            }
        }
        //兩個數組相互比較大小,其中一個數組比較完了。還剩左邊數組還有一些數字,那麼直接添加到temp後面
        while(first1 <= end1){
            temp[p++] = array[first1++];
        }
        while(first2 <= end2){
            temp[p++] = array[first2++];
        }
        //此時的array是合併以後,有序的
        for(int i=0; i<p; i++){
            array[first+i]=temp[i];
        }
    }
}

  

題目-兩個鏈表的第一個公共結點

輸入兩個鏈表,找出它們的第一個公共結點。(注意由於傳入數據是鏈表,因此錯誤測試數據的提示是用其餘方式顯示的,保證傳入數據是正確的) 

思路

 

由鏈表結點的定義,看出兩個鏈表是單向鏈表,若是單向鏈表有公共的結點,那麼這兩個鏈表從某一結點開始,它們的next都指向同一個結點。而且單向鏈表的每一個結點都只有一個next,那麼從第一個公共結點開始,它們的next都是指向同一結點,所以以後的全部結點都是重合的,不可能再出現分叉。

因爲兩個鏈表的長度可能不一致,所以爲了讓兩個鏈表的指針同時到達交點,須要讓長鏈表先走一段距離,再同時在兩個鏈表上遍歷。

若是訪問鏈表A的指針訪問到鏈表尾部,那麼從鏈表B的頭部開始從新訪問鏈表B。//以此實現讓長鏈表先走一個結點。

 

因爲設第一個鏈表沒交集的一段=a,第二個鏈表沒交集的一段=b,交集一段=c。

那麼a+c+b=b+c+a,所以兩個鏈表會在交集處相遇。

解法

/*
public class ListNode {
    int val;
    ListNode next = null;
 
    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode l1 = pHead1, l2 = pHead2;
        //使得A和B兩個鏈表的指針能同時訪問到交點。即長的先走一段距離,當長度相等時一塊兒向前走
        while(l1 != l2){
            //若是訪問鏈表A的指針訪問到鏈表尾部,那麼從鏈表B的頭部開始從新訪問鏈表B
            l1 = (l1==null)?pHead2:l1.next;
            //若是訪問鏈表B的指針訪問到鏈表尾部,那麼從鏈表A的頭部開始從新訪問鏈表A
            l2 = (l2==null)?pHead1:l2.next;
        }
        return l1;
    }
}
相關文章
相關標籤/搜索