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);