在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
输入一个数组,求出这个数组中的逆序对的总数P。
将P对1000000007取模的结果输出。 即输出P%1000000007
输入描述:
题目保证输入的数组中没有的相同的数字
数据范围:
对于%50的数据,size<=10^4
对于%75的数据,size<=10^5
对于%100的数据,size<=2*10^5
示例:
输入
1,2,3,4,5,6,7,0
输出
7
思路:暴力解法是双层循环逐一判断,时间复杂度为O(n^2). 本题的更优解法是分治与合并。该题的思路过程可以参考剑指OFFER上的解释,不再赘述,下面只写出我的思路。
首先,有序数组是一种最原始的数据结构,但在很多算法上因其有序性而比无序数组快很多。数组从无序到有序的排序算法的时间复杂度普遍是O(nlogn),所以若一个无序数组的某种算法超过这个复杂度后,可以考虑先将其进行排序。
其次,分治法由两部分组成:划分成子问题递归、将子问题合并。分治法时间复杂度的关键在于合并的复杂度。事实上所有问题都能分治,但只有合并算法够简单时,分治法才有意义(关于这一点可以参考我的这篇博文)。
最后讨论本题,可以先把原数组分成两个子数组,统计两个子数组内部的逆序对,再统计两个数组之间的逆序对,两者之和即为总的逆序对数。可问题来了,我们来求两个子数组之间的逆序对,从前面的子数组中依次取出一个数(n/2个数),与后面的子数组中的每个数进行比较(n/2个数),所以合并的时间复杂度是O(n^2),根据博文可以计算出总的时间复杂度,发现没有任何优化!
根据上面三点,一个很自然的想法出现了:在统计逆序对的同时进行排序。即计算逆序数的函数同时也要负起排序的任务。当两个子数组均是有序的时候,子数组之间的逆序数的计算会非常简单:用两个下标从两个数组的尾部(值较大的一端)开始向左滑动,假设这两个序号分别是 i 和 j ,分别对应两个数值 ai 和 bj (a数组在b数组之前),有:
若 ai>bj, 则 ai 大于 bj 及 bj 左边的所有数;
若 ai
对数组的排序过程用分治法,其中划分的部分和统计逆序对数是一致的,所以共同完成;而合并的部分就是将两个有序的子数组合并成大的有序数组,这需要额外的存储空间。比如数组nums[0,9] 划分成a[0,4]和b[5,9],合并a和b时需要有一个长度为10的辅助空间copy[0,9],存放排好序的元素。
当递归到上一层时,不需要把copy的值复制给nums,而是调换两者的位置(这一点非常重要)。上一层的copy就是下一层的nums, 上一层的nums就是下一层的copy。 从而我们需要合并的数组总是当前排序程度更优的那一个,而当前排序程度更差的那个数组作为辅助空间,来存储当前合并排序过后的元素。
代码:
//函数功能:把data数组的[start,end]区间排序后存储在copy数组的[start,end]区间内;
// 同时统计该区间内的逆序对数。
// 在区间[start,end]内data和copy的元素相同(虽然顺序不同)。
long long InversePairsCore(vector &data, vector ©, int start, int end){
if (start == end){
copy[start] = data[start];
return 0;
}
int length = (end - start) / 2;
//区间[start,end]一分为二:[start,mid]和[mid+1,end];
//把copy数组的左部分排序并存入data左部分,统计左部分的逆序数left
long long left = InversePairsCore(copy, data, start, start + length);
//把copy数组的右部分排序并存入data右部分,统计右部分的逆序数right
long long right = InversePairsCore(copy, data, start + length + 1, end);
//把两部分有序的data合并然后存入copy,统计左部分与右部分之间的逆序数count
int i = start + length;
int j = end;
int indexcopy = end;
long long count = 0;
while (i >= start&&j >= start + length + 1){
if (data[i]>data[j]){
copy[indexcopy--] = data[i--];
count+=j - start - length;
}
else
copy[indexcopy--] = data[j--];
}
//左右部分中有一个已经取完,则另一个依次加入copy即可
for (; i >= start; i--)
copy[indexcopy--] = data[i];
for (; j >= start + length + 1; j--)
copy[indexcopy--] = data[j];
return left + right + count;
}