《剑指offer》-- 数组中的逆序对、最小的K个数、从1到n整数中1出现的次数、正则表达式匹配、数值的整数次方
一、數組中的逆序對:
1、題目:
數組中的兩個數字,如果前面一個數字大于后面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數P。并將P對1000000007取模的結果輸出。 即輸出P%1000000007。
2、解題方法:
參考牛客網的“rs勿忘初心”、“流痕”:https://www.nowcoder.com/questionTerminal/96bd6684e04a44eb80e6a68efc0ec6c5
(1)看到這個題目,我們的第一反應是順序掃描整個數組。每掃描到一個數組的時候,逐個比較該數字和它后面的數字的大小。如果后面的數字比它小,則這兩個數字就組成了一個逆序對。假設數組中含有n個數字。由于每個數字都要和O(n)這個數字比較,因此這個算法的時間復雜度為O(n^2)。
(2)我們以數組{7,5,6,4}為例來分析統計逆序對的過程。每次掃描到一個數字的時候,我們不拿它和后面的每一個數字作比較,否則時間復雜度就是O(n^2),因此我們可以考慮先比較兩個相鄰的數字。
(a) 把長度為4的數組分解成兩個長度為2的子數組;
(b) 把長度為2的數組分解成兩個成都為1的子數組;
(c) 把長度為1的子數組 合并、排序并統計逆序對 ;
(d) 把長度為2的子數組合并、排序,并統計逆序對;
在上圖(a)和(b)中,我們先把數組分解成兩個長度為2的子數組,再把這兩個子數組分別拆成兩個長度為1的子數組。接下來一邊合并相鄰的子數組,一邊統計逆序對的數目。在第一對長度為1的子數組{7}、{5}中7大于5,因此(7,5)組成一個逆序對。同樣在第二對長度為1的子數組{6}、{4}中也有逆序對(6,4)。由于我們已經統計了這兩對子數組內部的逆序對,因此需要把這兩對子數組 排序 如上圖(c)所示, 以免在以后的統計過程中再重復統計。
(3)接下來我們統計兩個長度為2的子數組子數組之間的逆序對。合并子數組并統計逆序對的過程如下圖如下圖所示。
我們先用兩個指針分別指向兩個子數組的末尾,并每次比較兩個指針指向的數字。如果第一個子數組中的數字大于第二個數組中的數字,則構成逆序對,并且逆序對的數目等于第二個子數組中剩余數字的個數,如下圖(a)和(c)所示。如果第一個數組的數字小于或等于第二個數組中的數字,則不構成逆序對,如圖b所示。每一次比較的時候,我們都把較大的數字從后面往前復制到一個輔助數組中,確保 輔助數組(記為copy) 中的數字是遞增排序的。在把較大的數字復制到輔助數組之后,把對應的指針向前移動一位,接下來進行下一輪比較。
(4)過程總結:先把數組分割成子數組,先統計出子數組內部的逆序對的數目,然后再統計出兩個相鄰子數組之間的逆序對的數目。在統計逆序對的過程中,還需要對數組進行排序。如果對排序算法很熟悉,我們不難發現這個過程實際上就是歸并排序。
3、代碼實現:
/*歸并排序的改進,把數據分成前后兩個數組(遞歸分到每個數組僅有一個數據項), 合并數組,合并時,出現前面的數組值array[i]大于后面數組值array[j]時;則后面 數組array[mid]~array[j]都是小于array[i]的,count +=j-mid; * *copy數組的作用: 對已經統計了逆序對的數組,我們需要對其排好序,以避免在以后的統計過程中再次重復統計,所以copy數組就是起到這個作用, 當然,這里的有序只是“局部有序”,整體來看還是無序的。既然copy數組是“有序”的,下一次就直接在這個基礎上進行統計就可以, 原始數據data用來充當原來copy數組的角色來保存“更加有序”的數組。因此在InversePairsCore()方法中調換array和copy數組的位置。 */ public class Test16 {public int InversePairs(int [] array) {if(array==null || array.length==0){return 0;}int[] copy = new int[array.length];for(int i=0;i<array.length;i++){copy[i]=array[i];}int count =InversePairsCore(array,copy,0,array.length-1);return count;}private int InversePairsCore(int[] array, int[] copy, int low, int high) {if(low == high){//low和high分別代表開始下標和末尾下標return 0;}int mid = (low+high)>>1;//中間位置下標int leftCount = InversePairsCore(copy,array,low,mid)%1000000007;//左邊部分的逆序對int rightCout = InversePairsCore(copy,array,mid+1,high)%1000000007;//右邊部分的逆序對int count = 0;int i = mid;//左邊部分的起始指針位置int j = high;//右邊部分的起始指針位置int locCopy = high;//復制數組的下標起始位置while(i>=low && j>mid){if(array[i]>array[j]){count = count + j-mid;copy[locCopy--] = array[i--];if(count>=1000000007){count%=1000000007;}}else{copy[locCopy--] = array[j--];}}//下面的兩個for循環用于處理邊界情況for(;i>=low;i--){copy[locCopy--] = array[i];}for(;j>mid;j--){copy[locCopy--] = array[j];}return (leftCount+rightCout+count)%1000000007;} }?
?
二、最小的K個數:
1、題目:
輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。
2、解題思路:
第一種:使用冒泡排序的思想,不過不需要全部進行排序,只需要對最外層的k層進行排序就可以了。
第二種:利用最大堆,每次只和堆頂比,如果比堆頂的數小,刪除堆頂,新數入堆。
第三種:使用快速排序的Partiton思想:
(1)我們選定數組第一個數作為基數pivot,通過快速排序,使得比pivot小的數都位于數組的左邊,比pivot大的數字都位于數組的右邊。(這幾個數字不一定是排序的)
(2)找去基數pivot的下標index,如果index等于k-1,返回調整好的數組的前K個數字。否則進入第3步。
(3)當index大于k-1時,high等于index-1,重復以上操作;當index小于k-1時,low等于index+1,重復以上操作。
3、代碼實現:
public class Test18 {//第三種:使用快速排序的Partiton思想://1、我們選定數組第一個數作為基數pivot,通過快速排序,使得比pivot小的數都位于數組的左邊,比pivot大的數字都位于數組的右邊。(這幾個數字不一定是排序的)//2、找去基數pivot的下標index,如果index等于k-1,返回調整好的數組的前K個數字。否則進入第3步。//3、當index大于k-1時,high等于index-1,重復以上操作;當index小于k-1時,low等于index+1,重復以上操作。public ArrayList<Integer> GetLeastNumbers_Solution3(int [] input, int k) {ArrayList<Integer> result = new ArrayList<Integer>();int length = input.length;if(length<k || k==0){return result;}findKMin(input,0,input.length-1,k);for(int i=0;i<k;i++){result.add(input[i]);}return result;}public void findKMin(int[] input,int low,int high,int k){if(low < high){int index = partition(input,low,high);if(index == k-1){return;}else if(index < k-1){findKMin(input,index+1,high,k);}else{findKMin(input,low,index-1,k);}}}//partition思想:private int partition(int[] input, int low, int high) {int pivot = input[low];while(low<high){//右邊指針左移while(high>low && input[high] > pivot){high--;}input[low] = input[high];//左邊指針右移while(low<high && input[low] <= pivot){low++;}input[high] = input[low];}input[low] = pivot;return low;}//第二種:利用最大堆,每次只和堆頂比,如果比堆頂的數小,刪除堆頂,新數入堆public ArrayList<Integer> GetLeastNumbers_Solution2(int [] input, int k) {ArrayList<Integer> result = new ArrayList<Integer>();int length = input.length;if(k>length || k==0){return result;}PriorityQueue<Integer> maxStack = new PriorityQueue<Integer>(k,new Comparator<Integer>(){@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}});for(int i=0;i<length;i++){if(maxStack.size() != k){maxStack.offer(input[i]);}else if(maxStack.peek()>input[i]){Integer temp = maxStack.poll();temp = null;maxStack.offer(input[i]);}}for(Integer integer : maxStack){result.add(integer);}return result;}//第一種:使用冒泡排序的思想,不過不需要全部進行排序,只需要對最外層的k層進行排序就可以了。public ArrayList<Integer> GetLeastNumbers_Solution1(int [] input, int k) {ArrayList<Integer> result = new ArrayList();if(k==0 || k>input.length){return result;}for(int i = 0;i<k;i++){for(int j=0;j<input.length-i-1;j++){if(input[j]<input[j+1]){Integer temp = input[j];input[j]=input[j+1];input[j+1]=temp;}}result.add(input[input.length-i-1]);}return result;} }?
?
三、從1到n整數中1出現的次數:
1、題目:
求出1~13的整數中1出現的次數,并算出100~1300的整數中1出現的次數?為此他特別數了一下1~13中包含1的數字有1、10、11、12、13因此共出現6次,但是對于后面問題他就沒轍了。ACMer希望你們幫幫他,并把問題更加普遍化,可以很快的求出任意非負整數區間中1出現的次數(從1 到 n 中1出現的次數)。
2、解題思路:
參考牛客網的“藍裙子的百合魂”:https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6
設N = abcde ,其中abcde分別為十進制中各位上的數字。 如果要計算百位上1出現的次數,它要受到3方面的影響:百位上的數字,百位以下(低位)的數字,百位以上(高位)的數字。
① 如果百位上數字為0,百位上可能出現1的次數由更高位決定。比如:12013,則可以知道百位出現1的情況可能是:100~199,1100~1199,2100~2199,,...,11100~11199,一共1200個。可以看出是由更高位數字(12)決定,并且等于更高位數字(12)乘以 當前位數(100)。
② 如果百位上數字為1,百位上可能出現1的次數不僅受更高位影響還受低位影響。比如:12113,則可以知道百位受高位影響出現的情況是:100~199,1100~1199,2100~2199,,....,11100~11199,一共1200個。和上面情況一樣,并且等于更高位數字(12)乘以 當前位數(100)。但同時它還受低位影響,百位出現1的情況是:12100~12113,一共14個,等于低位數字(13)+1。
③ 如果百位上數字大于1(2~9),則百位上出現1的情況僅由更高位決定,比如12213,則百位出現1的情況是:100~199,1100~1199,2100~2199,...,11100~11199,12100~12199,一共有1300個,并且等于更高位數字+1(12+1)乘以當前位數(100)。
3、代碼實現:
public class Test30 {public int NumberOf1Between1AndN_Solution(int n) {int count = 0;//1的個數int i = 1;//當前位int current = 0,after = 0,before = 0;while((n/i)!= 0){current = (n/i)%10; //當前位數字before = n/(i*10); //高位數字after = n-(n/i)*i; //低位數字//如果為0,出現1的次數由高位決定,等于 高位數字 * 當前位數if (current == 0)count += before*i;//如果為1,出現1的次數由高位和低位決定,等于 高位*當前位+低位+1else if(current == 1)count += before * i + after + 1;//如果大于1,出現1的次數由高位決定,等于(高位數字+1)* 當前位數else{count += (before + 1) * i;}//前移一位i = i*10;}return count;} }?
?
四、正則表達式匹配:
1、題目:
請實現一個函數用來匹配包括'.'和'*'的正則表達式。模式中的字符'.'表示任意一個字符,而'*'表示它前面的字符可以出現任意次(包含0次)。 在本題中,匹配是指字符串的所有字符匹配整個模式。例如,字符串"aaa"與模式"a.a"和"ab*ac*a"匹配,但是與"aa.a"和"ab*a"均不匹配
2、解題思路:
參考牛客網的“披薩大叔”:https://www.nowcoder.com/questionTerminal/45327ae22b7b413ea21df13ee7d6429c
2.1 當模式中的第二個字符不是“*”時:
(1)如果字符串第一個字符和模式中的第一個字符相匹配,那么字符串和模式都后移一個字符,然后匹配剩余的。
(2)如果 字符串第一個字符和模式中的第一個字符相不匹配,直接返回false。
2.2 而當模式中的第二個字符是“*”時:
如果字符串第一個字符跟模式第一個字符不匹配,則模式后移2個字符,繼續匹配。如果字符串第一個字符跟模式第一個字符匹配,可以有3種匹配方式:
(1)模式后移2字符,相當于x*被忽略;
(2)字符串后移1字符,模式后移2字符;
(3)字符串后移1字符,模式不變,即繼續匹配字符下一位,因為*可以匹配多位;
3、代碼實現:
public class Test31 {public boolean match(char[] str, char[] pattern){if(str == null || pattern == null){return false;}int strIndex = 0;int patternIndex = 0;return matchCore(str,strIndex,pattern,patternIndex);}private boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex) {//有效性檢查:str到尾,pattern到尾,匹配成功if(strIndex == str.length && patternIndex == pattern.length)return true;//如果pattern先匹配到末尾,匹配失敗if(strIndex != str.length && patternIndex == pattern.length)return false;//模式第2個是*,且字符串第1個跟模式第1個匹配,分3種匹配模式;如不匹配,模式后移2位if(patternIndex+1 < pattern.length && pattern[patternIndex+1] =='*'){if((strIndex != str.length && str[strIndex] == pattern[patternIndex]) || (strIndex != str.length && pattern[patternIndex] == '.')){return matchCore(str,strIndex,pattern,patternIndex+2) //模式后移兩位,相當于x*被忽略,即x*匹配0個字符|| matchCore(str,strIndex+1,pattern,patternIndex+2) //匹配中一個字符,字符串后移1為,模式后移兩位|| matchCore(str,strIndex+1,pattern,patternIndex); //匹配一個,在匹配str中的下一個字符,因為*可以匹配多個字符}else{return matchCore(str,strIndex,pattern,patternIndex+2);}}//模式第2個不是*,且字符串第1個跟模式第1個匹配,則都后移1位,否則直接返回falseif((strIndex != str.length && str[strIndex] == pattern[patternIndex]) || (strIndex != str.length && pattern[patternIndex] == '.')){return matchCore(str,strIndex+1,pattern,patternIndex+1);}return false;} }?
?
五、數值的整數次方:
1、題目描述:
給定一個double類型的浮點數base和int類型的整數exponent。求base的exponent次方。
2、代碼實現:
public class Solution{public double Power(double base, int exponent) {double result = 1.0;if(exponent==0){return 1;}else if(exponent > 0 ){for(int i=0;i<exponent;i++)result *=base;}else{if(base==0){throw new RuntimeException("分母不能為零"); }for(int j=1;j<=-exponent;j++)result *=base;}return exponent>0?result:(1/result);} }?
?
總結
以上是生活随笔為你收集整理的《剑指offer》-- 数组中的逆序对、最小的K个数、从1到n整数中1出现的次数、正则表达式匹配、数值的整数次方的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《剑指offer》-- 回溯法:矩阵中的
- 下一篇: Eclipse:Target runti