题目描述:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
思路:
归并排序的改进。
把数据分成前后两个数组(递归分到每个数组仅有一个数据项),
合并数组,合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面
数组array[i]大于后面数组array[mid+1]~array[j],count += j-(mid+1)+1=j-mid
易错点:测试用例输出结果比较大可能溢出,要对每次返回的count mod(1000000007)求余,否则结果出错
看到这个题目,我们的第一反应是顺序扫描整个数组。每扫描到一个数组的时候,逐个比较该数字和它后面的数字的大小。 如果后面的数字比它小,则这两个数字就组成了一个逆序对。假设数组中含有n个数字。 由于每个数字都要和O(n)这个数字比较,因此这个算法的时间复杂度为O(n^2)。 我们以数组{7,5,6,4}为例来分析统计逆序对的过程。每次扫描到一个数字的时候,我们不拿ta和后面的每一个数字作比较, 否则时间复杂度就是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)所示, 以免在以后的统计过程中再重复统计。接下来我们统计两个长度为2的子数组子数组之间的逆序对。 合并子数组并统计逆序对的过程如下图如下图所示。我们先用两个指针分别指向两个子数组的末尾, 并每次比较两个指针指向的数字。如果第一个子数组中的数字大于第二个数组中的数字,则构成逆序对, 并且逆序对的数目等于第二个子数组中剩余数字的个数,如下图(a)和(c)所示。 如果第一个数组的数字小于或等于第二个数组中的数字,则不构成逆序对,如图b所示。 每一次比较的时候,我们都把较大的数字从后面往前复制到一个辅助数组中,确保 辅助数组 (记为copy)中的数字是递增排序的。在把较大的数字复制到辅助数组之后,把对应的指针向前移动一位, 接下来进行下一轮比较。
过程:先把数组分割成子数组,先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序。如果对排序算法很熟悉,我们不难发现这个过程实际上就是归并排序。参考代码如下:
package swordToOffer._35_InversePairs;
public class Solution {
public int InversePairs(int[] array) {
if (array == null || array.length == 0)
return 0;
int[] temp=new int[array.length];
int total=MergeSortCount(array,0,array.length-1,temp);
return total;
}
/*
返回逆序对数
*/
public int MergeSortCount(int[] arr, int low, int high, int[] temp) {
//分到最小,设置递归结束条件,分治到最后是只有一个数,比如下标为1,此时就直接计算逻辑不要再左右递归了
if (low == high || low > high) {
return 0;
}
int mid = (low + high) / 2;
// 左递归和右递归
int leftCount = MergeSortCount(arr, low, mid, temp);
int rightCount = MergeSortCount(arr, mid + 1, high, temp);
int count = 0;
//处理逻辑
int i = mid;//左数组的最后一位
int j = high;//右数组的最后一位
int t = high;//指向temp的high位
while (i >= low && j >= (mid + 1)) {
//两组有序数组中,选大的放入temp中,从最后开始,并计算count
if (arr[i] > arr[j]) {
//由于是升序,此时共有j-(mid+1)+1=j-mid个数比arr[i]小
count = count + j - mid;//左边比右边大时才需要计算逆序对
temp[t--] = arr[i--];
if(count>=1000000007)//!!!重要数值过大求余,否则会溢出int范围,造成结果错误!!!
{
count%=1000000007;
}
} else {
temp[t--] = arr[j--];
}
}
//其中一方放完,将剩下的一方都放入temp中
while (i >= low) {
temp[t--] = arr[i--];
}
while (j >= (mid + 1)) {
temp[t--] = arr[j--];
}
//将temp复制到arr中
for (int l = low; l <= high; l++) {
arr[l] = temp[l];
}
//返回结果,逆序对个数是 左数组内部的逆序对+右数组内部逆序对+左右之间的逆序对
return (leftCount + rightCount + count) % 1000000007;
}
}