public class Solution { private int count = 0; /*討論說能夠用分治法——歸併排序的思路喔*/ public int InversePairs(int [] array) { if(array.length <= 0)return 0; mergeSortAndCount(array); return count % 1000000007; } private void mergeSortAndCount(int[] a) { if(a.length == 1)return; int[] b, c; if(a.length % 2 == 0) {//數組元素個數爲偶數的狀況 b = new int[a.length / 2]; c = new int[a.length / 2]; for(int i = 0, j = a.length / 2, k = 0; i < a.length / 2 && j < a.length; i++, j++, k++) { b[k] = a[i]; c[k] = a[j]; } } else { b = new int[a.length / 2 + 1]; c = new int[a.length / 2]; for(int i = 0, j = a.length / 2 + 1, k = 0; i <= a.length / 2 || j < a.length; i++, j++, k++) { //這裏b數組要初始化的長度是長一點的 b[k] = a[i]; if(j < a.length)c[k] = a[j]; } } mergeSortAndCount(b); mergeSortAndCount(c); mergeAndCount(b, c, a);//把已經排好序的b和c數組合並而且計算逆序,合併到a數組中去。 } private void mergeAndCount(int[] b, int[] c, int[] a) { int i = 0, j = 0, k = 0; while(i < b.length && j < c.length) { if(b[i] <= c[j]) {//正常狀況,非逆序 a[k ++] = b[i ++]; } else {//b的元素大於c的狀況,逆序 count = count + b.length - i;//由於這個數組是排好序的,b[i]元素大於c[j],意味着b[i]-b[a.length - 1]的元素都大於它 a[k ++] = c[j ++]; } } while(i < b.length)a[k ++] = b[i ++]; while(j < c.length)a[k ++] = c[j ++]; } }
改造後的歸併:數組
這裏全程就用到一個輔助數組,和一個自己就要排序的數組。測試
內部在處理遞歸的時候用的都是index,而後要說明的是,輔助數組和排序數組的身份是互換的。原本應該是:在原數組的兩部分排序好的數組要合併嘛,就借一個輔助數組放合併的數據,而後再複製回原來的數組。spa
但如今咱們用了一個整數i,來交替完成這個過程,反正只要最後一次合併是合併到要排序的那個數組就行了嘛。.net
這裏有個也是隻用了一個輔助數組,但不是交替用的例子,這個其實好理解點hh:code
https://blog.csdn.net/abc7845129630/article/details/52740746blog
而後上代碼:排序
public class Solution { private long count = 0;//類變量,逆序對的數量 /*減小輔助數組的歸併法,或者說只用一個輔助數組的歸併法*/ public int InversePairs(int [] array) { if(array.length <= 0)return 0; int i = 0; int[] help = new int[array.length];//輔助數組,就用這一個就夠了,因此空間複雜度爲O(n) mergeAndCountSort(array, 0, array.length - 1, i, help); return (int)(count % 1000000007); } /** * 對a數組中的begin到end元素進行歸併排序,若是i是奇數則排序後的數組合併到b數組中去(b[begin-end]有序);不然就存到a數組中去(a[begin-end]有序) * 之因此這樣作是由於能夠避免利用了輔助數組後還有複製元素到原來數組的操做 * 由於咱們最後要排序的是a數組嘛,因此在主程序中調用這個sort應該傳i=0或者是其餘偶數 * @param a * @param begin * @param end * @param i * @param b */ private void mergeAndCountSort(int[] a, int begin, int end, int i, int[] b) { if (begin == end) {// 遞歸終結條件 // 一開始是沒有加這句的,若是排序的數組是偶數個的話,就沒事,奇數個的話就有事。 if (i % 2 == 1) b[begin] = a[begin];// 奇數,這個結果要到b數組中去,不然不用動 } else { int middle = (begin + end) / 2;// 中間index // 分別對左半部分和右半部分作遞歸的歸併排序,i + 1保證了下次用另外一個數組作輔助數組 mergeAndCountSort(a, begin, middle, i + 1, b); mergeAndCountSort(a, middle + 1, end, i + 1, b); if (i % 2 == 1) { // i是奇數,那麼歸併後數組要合併到b數組中去 mergeAndCount(a, begin, middle, end, b); } else { // i是偶數,那麼歸併後的數組要合併到a數組去 mergeAndCount(b, begin, middle, end, a); } } } /** * 帶有數逆序對的合併有序數組的方法,合併的過程當中順便數逆序對 * 將a數組中的[begin-middle]有序對和[middle + 1-end]有序對合並,合併結果到result數組中去,result數組的[begin-end]有序 * @param a * @param begin * @param middle * @param end * @param result */ private void mergeAndCount(int[] a, int begin, int middle, int end, int[] result) { int i = begin, j = middle + 1, k = begin; while(i <= middle && j <= end) { if(a[i] <= a[j]) { //左邊的元素小於右邊的,正常狀況,不是逆序對 result[k++] = a[i++]; } else { //左邊的元素大於右邊的,逆序狀況 //由於這個數組是排好序的,因此意味着左邊部分後面的元素也大於這個右邊部分的這個元素,而後這裏要提早加上去 //由於小的元素要被加到result中去,等等其餘的就不會碰到它了 count = count + (middle - i + 1); result[k++] = a[j++]; } } while(i <= middle) result[k++] = a[i++]; while(j <= end) result[k++] = a[j++]; } }
我剛改完這個歸併的時候,在本地跑能夠,而後排序也有效果,結果特麼仍是同樣的結果????遞歸
%50同樣卡在那個位置……我佛了。io
查了好久,結合那個數據猜發現,這個給的例子就算是在新的網頁中打開,也是還沒結束的:class
也就是說,這個逆序對的個數,應該是能夠超過Int的範圍的……
因此就像上面貼的代碼同樣,把int的count改爲long,返回的時候再轉換成int才經過。
這裏講一下這個歸併法的一些要注意的地方:
1. 就是剛剛說的,逆序個數會大於int的最大值的問題,要用long。
2. 在遞歸中,原本begin == end就能夠返回了,由於就有序了嘛,但這裏由於涉及到複製來複制去,還有加個i的判斷,若是爲奇數要把這個數搞到b數組中去。(測試過,若是待排序的數字個數是偶數個好像就沒有問題,是奇數個就會有問題)
3. 就是在數逆序對的時候的問題了,由於是排好序的,並且小的那個會進入目標數組排隊,因此發現了左邊的元素大於右邊的元素時,不是隻count++:
else { //左邊的元素大於右邊的,逆序狀況 //由於這個數組是排好序的,因此意味着左邊部分後面的元素也大於這個右邊部分的這個元素,而後這裏要提早加上去 //由於小的元素要被加到result中去,等等其餘的就不會碰到它了 count = count + (middle - i + 1);