《剑指offer》-- 数组中的逆序对、最小的K个数、从1到n整数中1出现的次数、正则表达式匹配、数值的整数次方
一、數(shù)組中的逆序?qū)?#xff1a;
1、題目:
數(shù)組中的兩個數(shù)字,如果前面一個數(shù)字大于后面的數(shù)字,則這兩個數(shù)字組成一個逆序?qū)Α]斎胍粋€數(shù)組,求出這個數(shù)組中的逆序?qū)Φ目倲?shù)P。并將P對1000000007取模的結(jié)果輸出。 即輸出P%1000000007。
2、解題方法:
參考??途W(wǎng)的“rs勿忘初心”、“流痕”:https://www.nowcoder.com/questionTerminal/96bd6684e04a44eb80e6a68efc0ec6c5
(1)看到這個題目,我們的第一反應(yīng)是順序掃描整個數(shù)組。每掃描到一個數(shù)組的時候,逐個比較該數(shù)字和它后面的數(shù)字的大小。如果后面的數(shù)字比它小,則這兩個數(shù)字就組成了一個逆序?qū)?。假設(shè)數(shù)組中含有n個數(shù)字。由于每個數(shù)字都要和O(n)這個數(shù)字比較,因此這個算法的時間復(fù)雜度為O(n^2)。
(2)我們以數(shù)組{7,5,6,4}為例來分析統(tǒng)計逆序?qū)Φ倪^程。每次掃描到一個數(shù)字的時候,我們不拿它和后面的每一個數(shù)字作比較,否則時間復(fù)雜度就是O(n^2),因此我們可以考慮先比較兩個相鄰的數(shù)字。
(a) 把長度為4的數(shù)組分解成兩個長度為2的子數(shù)組;
(b) 把長度為2的數(shù)組分解成兩個成都為1的子數(shù)組;
(c) 把長度為1的子數(shù)組 合并、排序并統(tǒng)計逆序?qū)?;
(d) 把長度為2的子數(shù)組合并、排序,并統(tǒng)計逆序?qū)?#xff1b;
在上圖(a)和(b)中,我們先把數(shù)組分解成兩個長度為2的子數(shù)組,再把這兩個子數(shù)組分別拆成兩個長度為1的子數(shù)組。接下來一邊合并相鄰的子數(shù)組,一邊統(tǒng)計逆序?qū)Φ臄?shù)目。在第一對長度為1的子數(shù)組{7}、{5}中7大于5,因此(7,5)組成一個逆序?qū)ΑM瑯釉诘诙﹂L度為1的子數(shù)組{6}、{4}中也有逆序?qū)?#xff08;6,4)。由于我們已經(jīng)統(tǒng)計了這兩對子數(shù)組內(nèi)部的逆序?qū)?#xff0c;因此需要把這兩對子數(shù)組 排序 如上圖(c)所示, 以免在以后的統(tǒng)計過程中再重復(fù)統(tǒng)計。
(3)接下來我們統(tǒng)計兩個長度為2的子數(shù)組子數(shù)組之間的逆序?qū)Α:喜⒆訑?shù)組并統(tǒng)計逆序?qū)Φ倪^程如下圖如下圖所示。
我們先用兩個指針分別指向兩個子數(shù)組的末尾,并每次比較兩個指針指向的數(shù)字。如果第一個子數(shù)組中的數(shù)字大于第二個數(shù)組中的數(shù)字,則構(gòu)成逆序?qū)?#xff0c;并且逆序?qū)Φ臄?shù)目等于第二個子數(shù)組中剩余數(shù)字的個數(shù),如下圖(a)和(c)所示。如果第一個數(shù)組的數(shù)字小于或等于第二個數(shù)組中的數(shù)字,則不構(gòu)成逆序?qū)?#xff0c;如圖b所示。每一次比較的時候,我們都把較大的數(shù)字從后面往前復(fù)制到一個輔助數(shù)組中,確保 輔助數(shù)組(記為copy) 中的數(shù)字是遞增排序的。在把較大的數(shù)字復(fù)制到輔助數(shù)組之后,把對應(yīng)的指針向前移動一位,接下來進(jìn)行下一輪比較。
(4)過程總結(jié):先把數(shù)組分割成子數(shù)組,先統(tǒng)計出子數(shù)組內(nèi)部的逆序?qū)Φ臄?shù)目,然后再統(tǒng)計出兩個相鄰子數(shù)組之間的逆序?qū)Φ臄?shù)目。在統(tǒng)計逆序?qū)Φ倪^程中,還需要對數(shù)組進(jìn)行排序。如果對排序算法很熟悉,我們不難發(fā)現(xiàn)這個過程實際上就是歸并排序。
3、代碼實現(xiàn):
/*歸并排序的改進(jìn),把數(shù)據(jù)分成前后兩個數(shù)組(遞歸分到每個數(shù)組僅有一個數(shù)據(jù)項), 合并數(shù)組,合并時,出現(xiàn)前面的數(shù)組值array[i]大于后面數(shù)組值array[j]時;則后面 數(shù)組array[mid]~array[j]都是小于array[i]的,count +=j-mid; * *copy數(shù)組的作用: 對已經(jīng)統(tǒng)計了逆序?qū)Φ臄?shù)組,我們需要對其排好序,以避免在以后的統(tǒng)計過程中再次重復(fù)統(tǒng)計,所以copy數(shù)組就是起到這個作用, 當(dāng)然,這里的有序只是“局部有序”,整體來看還是無序的。既然copy數(shù)組是“有序”的,下一次就直接在這個基礎(chǔ)上進(jìn)行統(tǒng)計就可以, 原始數(shù)據(jù)data用來充當(dāng)原來copy數(shù)組的角色來保存“更加有序”的數(shù)組。因此在InversePairsCore()方法中調(diào)換array和copy數(shù)組的位置。 */ 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分別代表開始下標(biāo)和末尾下標(biāo)return 0;}int mid = (low+high)>>1;//中間位置下標(biāo)int leftCount = InversePairsCore(copy,array,low,mid)%1000000007;//左邊部分的逆序?qū)nt rightCout = InversePairsCore(copy,array,mid+1,high)%1000000007;//右邊部分的逆序?qū)nt count = 0;int i = mid;//左邊部分的起始指針位置int j = high;//右邊部分的起始指針位置int locCopy = high;//復(fù)制數(shù)組的下標(biāo)起始位置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循環(huán)用于處理邊界情況for(;i>=low;i--){copy[locCopy--] = array[i];}for(;j>mid;j--){copy[locCopy--] = array[j];}return (leftCount+rightCout+count)%1000000007;} }?
?
二、最小的K個數(shù):
1、題目:
輸入n個整數(shù),找出其中最小的K個數(shù)。例如輸入4,5,1,6,2,7,3,8這8個數(shù)字,則最小的4個數(shù)字是1,2,3,4,。
2、解題思路:
第一種:使用冒泡排序的思想,不過不需要全部進(jìn)行排序,只需要對最外層的k層進(jìn)行排序就可以了。
第二種:利用最大堆,每次只和堆頂比,如果比堆頂?shù)臄?shù)小,刪除堆頂,新數(shù)入堆。
第三種:使用快速排序的Partiton思想:
(1)我們選定數(shù)組第一個數(shù)作為基數(shù)pivot,通過快速排序,使得比pivot小的數(shù)都位于數(shù)組的左邊,比pivot大的數(shù)字都位于數(shù)組的右邊。(這幾個數(shù)字不一定是排序的)
(2)找去基數(shù)pivot的下標(biāo)index,如果index等于k-1,返回調(diào)整好的數(shù)組的前K個數(shù)字。否則進(jìn)入第3步。
(3)當(dāng)index大于k-1時,high等于index-1,重復(fù)以上操作;當(dāng)index小于k-1時,low等于index+1,重復(fù)以上操作。
3、代碼實現(xiàn):
public class Test18 {//第三種:使用快速排序的Partiton思想://1、我們選定數(shù)組第一個數(shù)作為基數(shù)pivot,通過快速排序,使得比pivot小的數(shù)都位于數(shù)組的左邊,比pivot大的數(shù)字都位于數(shù)組的右邊。(這幾個數(shù)字不一定是排序的)//2、找去基數(shù)pivot的下標(biāo)index,如果index等于k-1,返回調(diào)整好的數(shù)組的前K個數(shù)字。否則進(jìn)入第3步。//3、當(dāng)index大于k-1時,high等于index-1,重復(fù)以上操作;當(dāng)index小于k-1時,low等于index+1,重復(fù)以上操作。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;}//第二種:利用最大堆,每次只和堆頂比,如果比堆頂?shù)臄?shù)小,刪除堆頂,新數(shù)入堆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;}//第一種:使用冒泡排序的思想,不過不需要全部進(jìn)行排序,只需要對最外層的k層進(jìn)行排序就可以了。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整數(shù)中1出現(xiàn)的次數(shù):
1、題目:
求出1~13的整數(shù)中1出現(xiàn)的次數(shù),并算出100~1300的整數(shù)中1出現(xiàn)的次數(shù)?為此他特別數(shù)了一下1~13中包含1的數(shù)字有1、10、11、12、13因此共出現(xiàn)6次,但是對于后面問題他就沒轍了。ACMer希望你們幫幫他,并把問題更加普遍化,可以很快的求出任意非負(fù)整數(shù)區(qū)間中1出現(xiàn)的次數(shù)(從1 到 n 中1出現(xiàn)的次數(shù))。
2、解題思路:
參考??途W(wǎng)的“藍(lán)裙子的百合魂”:https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6
設(shè)N = abcde ,其中abcde分別為十進(jìn)制中各位上的數(shù)字。 如果要計算百位上1出現(xiàn)的次數(shù),它要受到3方面的影響:百位上的數(shù)字,百位以下(低位)的數(shù)字,百位以上(高位)的數(shù)字。
① 如果百位上數(shù)字為0,百位上可能出現(xiàn)1的次數(shù)由更高位決定。比如:12013,則可以知道百位出現(xiàn)1的情況可能是:100~199,1100~1199,2100~2199,,...,11100~11199,一共1200個。可以看出是由更高位數(shù)字(12)決定,并且等于更高位數(shù)字(12)乘以 當(dāng)前位數(shù)(100)。
② 如果百位上數(shù)字為1,百位上可能出現(xiàn)1的次數(shù)不僅受更高位影響還受低位影響。比如:12113,則可以知道百位受高位影響出現(xiàn)的情況是:100~199,1100~1199,2100~2199,,....,11100~11199,一共1200個。和上面情況一樣,并且等于更高位數(shù)字(12)乘以 當(dāng)前位數(shù)(100)。但同時它還受低位影響,百位出現(xiàn)1的情況是:12100~12113,一共14個,等于低位數(shù)字(13)+1。
③ 如果百位上數(shù)字大于1(2~9),則百位上出現(xiàn)1的情況僅由更高位決定,比如12213,則百位出現(xiàn)1的情況是:100~199,1100~1199,2100~2199,...,11100~11199,12100~12199,一共有1300個,并且等于更高位數(shù)字+1(12+1)乘以當(dāng)前位數(shù)(100)。
3、代碼實現(xiàn):
public class Test30 {public int NumberOf1Between1AndN_Solution(int n) {int count = 0;//1的個數(shù)int i = 1;//當(dāng)前位int current = 0,after = 0,before = 0;while((n/i)!= 0){current = (n/i)%10; //當(dāng)前位數(shù)字before = n/(i*10); //高位數(shù)字after = n-(n/i)*i; //低位數(shù)字//如果為0,出現(xiàn)1的次數(shù)由高位決定,等于 高位數(shù)字 * 當(dāng)前位數(shù)if (current == 0)count += before*i;//如果為1,出現(xiàn)1的次數(shù)由高位和低位決定,等于 高位*當(dāng)前位+低位+1else if(current == 1)count += before * i + after + 1;//如果大于1,出現(xiàn)1的次數(shù)由高位決定,等于(高位數(shù)字+1)* 當(dāng)前位數(shù)else{count += (before + 1) * i;}//前移一位i = i*10;}return count;} }?
?
四、正則表達(dá)式匹配:
1、題目:
請實現(xiàn)一個函數(shù)用來匹配包括'.'和'*'的正則表達(dá)式。模式中的字符'.'表示任意一個字符,而'*'表示它前面的字符可以出現(xiàn)任意次(包含0次)。 在本題中,匹配是指字符串的所有字符匹配整個模式。例如,字符串"aaa"與模式"a.a"和"ab*ac*a"匹配,但是與"aa.a"和"ab*a"均不匹配
2、解題思路:
參考??途W(wǎng)的“披薩大叔”:https://www.nowcoder.com/questionTerminal/45327ae22b7b413ea21df13ee7d6429c
2.1 當(dāng)模式中的第二個字符不是“*”時:
(1)如果字符串第一個字符和模式中的第一個字符相匹配,那么字符串和模式都后移一個字符,然后匹配剩余的。
(2)如果 字符串第一個字符和模式中的第一個字符相不匹配,直接返回false。
2.2 而當(dāng)模式中的第二個字符是“*”時:
如果字符串第一個字符跟模式第一個字符不匹配,則模式后移2個字符,繼續(xù)匹配。如果字符串第一個字符跟模式第一個字符匹配,可以有3種匹配方式:
(1)模式后移2字符,相當(dāng)于x*被忽略;
(2)字符串后移1字符,模式后移2字符;
(3)字符串后移1字符,模式不變,即繼續(xù)匹配字符下一位,因為*可以匹配多位;
3、代碼實現(xiàn):
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) //模式后移兩位,相當(dāng)于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;} }?
?
五、數(shù)值的整數(shù)次方:
1、題目描述:
給定一個double類型的浮點數(shù)base和int類型的整數(shù)exponent。求base的exponent次方。
2、代碼實現(xiàn):
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);} }?
?
總結(jié)
以上是生活随笔為你收集整理的《剑指offer》-- 数组中的逆序对、最小的K个数、从1到n整数中1出现的次数、正则表达式匹配、数值的整数次方的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《剑指offer》-- 回溯法:矩阵中的
- 下一篇: Eclipse:Target runti