题目:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。例如,在数组{7,5,6,4}中,一共存在5个逆序对,分别是(7,6)、(7,5)、(7,4)、(6,4)和(5.4)。
分析:
第一反应是顺序扫描整个数组,每扫描到一个数字,比较这个数字和它后面的数字的大小,如果后面的数字比它小,就构成一个逆序对。这种方法时间复杂度是O(n²)。
逆序数的计算可以结合归并排序来统计。回忆归并排序的过程:将一个数组平均分成左右两部分,对于左右部分,继续分隔成左右两部分,直到每部分的长度为1。此时,因为每部分只有一个元素,所以可以看做每部分都是有序的,接下来,对相邻的两部分进行合并,在合并的过程中,统计逆序数的个数。假设第一个部分是6789,第二个部分是1234,合并这两个部分,第一部分下标指向数字6,第二部分下标指向数字1,此时6>1,按照归并排序的要求,需要将小的那个数放入临时数组,让小的那部分下标后移,再继续下面的操作,我们需要在这里加上一步,当前部分的值大于后部分的值时候,就出现了逆序对,比如6>1,我们知道第一部分是有序的,又因为6>1,于是7>1,8>1,9>1也成立,所以根据6>1得出的逆序对的数量是4个(mid-left+1),继续往后看,第一部分下标指向6,第二部分下标指向2,6>2,同理7,8,9也大于2,用同样的方法mid-left+1,继续累加逆序对。直到p1<=mid && p2<=right不成立了,说明有一边已经遍历完了。此时,将没有遍历完的部分,依次放入临时数组即可。最后将临时数组里的数据覆盖到原数组中即可。
此时的时间复杂度是O(nlogn)。
解法:
package com.wsy;
public class Main {
public static int[] array;
public static int count;
public static void main(String[] args) {
array = new int[]{7, 5, 6, 4};
int length = array.length;
mergeSort(0, length - 1);
System.out.println("逆序对个数为:" + count);
}
public static void mergeSort(int left, int right) {
if (left < right) {
int mid = (left + right) >> 1;
mergeSort(left, mid);
mergeSort(mid + 1, right);
merge(left, mid, right);
}
}
public static void merge(int left, int mid, int right) {
int p1 = left, p2 = mid + 1, p3 = 0;
int[] temp = new int[right - left + 1];
while (p1 <= mid && p2 <= right) {
if (array[p1] < array[p2]) {
temp[p3++] = array[p1++];
} else {
count += mid - p1 + 1;
temp[p3++] = array[p2++];
}
}
while (p1 <= mid) {
temp[p3++] = array[p1++];
}
while (p2 <= right) {
temp[p3++] = array[p2++];
}
for (int i = left; i <= right; i++) {
array[i] = temp[i - left];
}
}
}