题目描述
- 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
- 地址:牛客地址
问题分析
- 暴力法:两层循环统计出逆序对,时间复杂度:O(N^2),数据量大时会超时
- 改写归并排序,利用归并排序中的分治思想,在两个有序的子数组进行merge合并操作时,统计出逆序对的数量,注意,此时子数组已经是有序的了,而原子数组内部的逆序对已经统计完了。也可以这么想,递归一步步深入下去,最终子数组只包含一个元素,然后在merge过程中,完成子数组的排序(只有两个元素),并且统计出逆序对,再一步步返回。
- 那么如何在merge过程中统计出两个已经排好序的子数组中的逆序对呢?
比如现有两个已经排好序的子数组[3,6,7] 和[2,4,5],两者在原数组中的形式是[3,6,7,2,4,5],那么在进行merge过程中,i 指向第一个子数组的开始处,mid 指向 第一个子数组的结尾处, j 指向第二个子数组的开始处,比较 arr[i] 与 arr[j],如果 arr[j] < arr[i] ,说明存在逆序对,并且 【i ~ mid】都大于 arr[j], 所以对于 arr[j]而言,它能产生的逆序对个数为 mid - i + 1 个,然后将 j后移。如果 arr[i] < arr[j] ,那么说明arr[j] 不会产生逆序对。
- 那么如何在merge过程中统计出两个已经排好序的子数组中的逆序对呢?
总结:若想统计数组【begin ~ end】中的逆序对,同样用分治的思想
- 先统计 【begin ~ mid】中的子数组内部逆序对 num1,并在统计过程中排序(递归)
- 再统计【mid + 1 ~ end】中的子数组内部逆序对 num2,并在统计过程中排序(递归)
- 再统计已经排好序的【begin ~ mid】 与【mid + 1 ~ end】在merge时子数组之间的逆序对
- 前三者相加,便是【begin ~ end】中的逆序对,其实也完成了排序
但要注意,统计逆序对,其实都发生在merge过程中。
时间复杂度:O(NlogN)
经验教训
- 归并排序
- 分治的思想
代码实现
public class Solution {
public int InversePairs(int [] array) {
if (array == null || array.length == 0) {
return 0;
}
return (int)(mergeSort(array, 0, array.length - 1) % 1000000007);
}
public long mergeSort(int[] arr, int begin, int end) {
if (begin == end) {
return 0;
}
int mid = begin + ((end - begin) >> 1);
long num1 = mergeSort(arr, begin, mid);
long num2 = mergeSort(arr, mid + 1, end);
long num3 = merge(arr, begin, mid, end);
return num1 + num2 + num3;
}
public long merge(int[] arr, int begin, int mid, int end) {
int[] temp = new int[end - begin + 1];
int i = begin;
int j = mid + 1;
int k = 0;
long count = 0;
while (i <= mid && j <= end) {
if (arr[i] > arr[j]) {
count += mid - i + 1;
temp[k++] = arr[j++];
}else {
temp[k++] = arr[i++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= end) {
temp[k++] = arr[j++];
}
i = 0;
while (i < end - begin + 1) {
arr[begin + i] = temp[i];
i++;
}
return count;
}
}