題目-數組中出現次數超過一半的數字 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的數字有一、10、11、12、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; } }
題目-把數組排成最小的數
輸入一個正整數數組,把數組裏全部數字拼接起來排成一個數,打印能拼接出的全部數字中最小的一個。例如輸入數組{3,32,321},則打印出這三個數字能排成的最小數字爲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; } }
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; } }