题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
分析
直接思路:顺序扫描整个数组,每次扫描到一个数字,将该数字逐个与后面的数字比较,如果满足逆序关系,则统计量count加1。如果有n个数字,每个数字都需要和 O(n) 个数字进行比较,则总的而时间复杂度为 O(n) 。
推荐解法:参考归并排序的思路,将大数组不断进行分割,分割成为左右两个子数组,分别对左右子数组进行递增排序并统计左右子数组中的逆序对的数目;再将左右子数组进行合并,统计合并之后的逆序对的的数目。归并排序是一个递归调用的过程,以 O(n) 的空间换取时间,时间复杂度为 O(nlogn) ,则总的逆序对数目为左子数组中、右子数组中、合并之后的逆序对数目的和。
牛客AC:
package com.problem;
public class InversePairs {
public static void main(String[] args) {
InversePairs inversePairs = new InversePairs();
int[] arr = {7, 5, 6, 4};
System.out.println(inversePairs.inversePairs(arr));
}
public int inversePairs(int[] arr) {
if(arr == null || arr.length <= 0)
return 0;
int length = arr.length;
// 复制一份数组,辅助空间
int[] copyArr = new int[length];
for(int i = 0; i < length; i++)
copyArr[i] = arr[i];
int count = getInversePairs(arr, copyArr, 0, length - 1);
return count;
}
/**
*
* @param iniArr 初始数组
* @param copyArr 辅助数组
* @param start 起始索引
* @param end 结束索引
* @return
*/
public int getInversePairs(int[] iniArr, int[] copyArr, int start, int end) {
if(start == end) {
copyArr[start] = iniArr[start];
return 0;
}
// 对左右子数组归并排序,排序过程中统计逆序数
int midLength = (end + start) / 2;
int leftCount = getInversePairs(iniArr, copyArr, start, midLength);
int rightCount = getInversePairs(iniArr, copyArr, midLength + 1, end);
// 归并
int mergeCount = mergeInversePairs(iniArr, copyArr, start, midLength + 1, end);
// 总的逆序数等于左右子数组中的逆序数加上合并之后的逆序数
return leftCount + rightCount + mergeCount;
}
/**
* 在归并排序过程中统计逆序数
* @param iniArr 初始数组
* @param copyArr 复制数组
* @param leftStart 左子数组起始索引
* @param rightStart 右子数组起始索引
* @param rightEnd 右子数组结束索引
* @return 合并之后的逆序数
*/
public int mergeInversePairs(int[] iniArr, int[] copyArr, int leftStart, int rightStart, int rightEnd) {
int leftEnd = rightStart - 1;
int indexCopy = rightEnd;
int nElements = rightEnd - leftStart + 1;
// i,j初始指向子数组的尾部
int i = leftEnd;
int j = rightEnd;
int count = 0; // 逆序数个数
while(i >= leftStart && j >= rightStart) {
// 由于左边子数组都是递增排序的,如果左边元素大于右边元素,逆序数为整个右子数组的个数
if(iniArr[i] > iniArr[j]) {
copyArr[indexCopy--] = iniArr[i--];
count += j - rightStart + 1;
} else {
copyArr[indexCopy--] = iniArr[j--];
}
}
// 复制左子数组剩余元素
while(i >= leftStart)
copyArr[indexCopy--] = iniArr[i--];
// 复制右子数组剩余元素
while(j >= rightStart)
copyArr[indexCopy--] = iniArr[j--];
// 将结果数组替换原数组,参与后续递归的归并排序
// 最后的结果iniArr和resArr数组都已经经过了排序,完全一致
for (int k = 0; k < nElements; k++) {
iniArr[rightEnd] = copyArr[rightEnd];
rightEnd--;
}
return count;
}
}
参考
1. 何海涛,剑指offer名企面试官精讲典型编程题(纪念版),电子工业出版社