0x01.問題
在數組中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數。0 <= 數組長度 <= 50000
輸入: [7,5,6,4]
輸出: 5
來自《劍指offer》
public int reversePairs(int[] nums)
0x02.解決思路
看問題,並不複雜,對於每一個數,只需要找出後面比它小的數的個數,最後累加起來,就是最終的答案,於是可以暴力的寫出兩層循環的代碼,但是,這個時間複雜度達到了O(N^2)
,對於五位數的數組長度來說,已經無法在短時間內計算出來了,所以必須進行更高效的優化。
再來回想一下我們看到這個問題的第一想法,逆序,大於,這些字眼,都刺激着我們,似乎可以排序,但仔細去想一下排序的思路,排完序的時候,整個數組已經變了, 裏面元素的關係也完全變了,而題目中的逆序,很明顯是跟元素之間的先後位置有關的,所以,很快就打消了排序的思路。
再仔細想想,好像暫時沒有更好的辦法了,對於這個問題的話,掃描數組一遍的話肯定是不可能的,也就是說,O(N)
的複雜度是不現實的,那麼我們就需要想辦法將這個複雜度降爲介於O(N^2)
和O(N)
複雜度,最容易想到的,那就是O(N*logN)
。對於這種複雜度,要麼遞歸,要麼二分。到這裏爲止,最爲符合的應該就是分治了,分而治之。
怎麼進行分治了,發現問題還是回到了排序了,我們雖然不能直接進行排序解決這個問題,但我們可以利用歸併排序的中間過程去計算數量,在這個中間過程,這個位置關係還是滿足的。
我們回顧一下歸併排序的具體做法,在合併的步驟中,應該是這樣的:
- 從左到右,依次比較一個元素,小者加入合併後的新數組。
在這個歸併的過程中,我們是不是可以確定沒一個元素和後面一個數組的大小關係,可以計算出比它大的數有多少,並且位置關係是完全滿足的。
-
可能你會有一個疑問,那麼,在當前這個子數組中呢?位置不是已經變了,嘛,變成排序之後的了。
-
你可能忘了歸併是先分解到最小纔開始排序的,也就是說,在數組中只有一個元素的時候,合併成這個數組後,這些數目已經計算進去了,並且是遞歸進去的。
在有了這條最基本的思路,我們可以去完善我們解決這個問題的大體思路了:
- 模擬歸併排序的過程,在每次排序的過程中,合成新數組之前,進行計數,計算符合題目要求的數組,最後累加返回。
0x03.解決代碼–歸併排序(分治)
class Solution {
private int mergeSort(int[] nums,int[] temp,int left,int right){
if(left>=right){
return 0;
}
int mid=(left+right)/2;
//分解的過程
int count=mergeSort(nums,temp,left,mid)+mergeSort(nums,temp,mid+1,right);
int i=left;//控制左邊下標
int j=mid+1;//控制右邊下標
int index=left;//控制新數組的下標
while(i<=mid&&j<=right){
if(nums[i]<=nums[j]){//左邊的小
temp[index++]=nums[i++];
count+=j-(mid+1);//歸併中符合條件的個數
}else{//右邊的小
temp[index++]=nums[j++];
}
}
//左邊有剩餘
for(int k=i;k<=mid;k++){
temp[index++]=nums[k];
count+=j-(mid+1);//每一個數都滿足條件
}
//右邊有剩餘
for(int k=j;k<=right;k++){
temp[index++]=nums[k];
}
//將已完成的temp拷貝到nums
for (int k = left; k <=right; k++){
nums[left+k-left] = temp[k];
}
return count;
}
public int reversePairs(int[] nums) {
int n=nums.length;
int[] temp=new int[n];//歸併過程中需要的新數組
return mergeSort(nums,temp,0,n-1);
}
}
ATFWUS --Writing By 2020–04-24