题目:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
思路:利用递归,归并排序思想。
1).先将原数组进行拆分,拆分到底变成每个数组只有1个元素。
2).再进行merge操作,merge操作是将已经有序的两个数组arr[l...mid]和arr[mid + 1...r]合并为一个大的有序数组arr[l...r],在合并的过程中,统计逆序对的个数,具体思路如下:
①定义一个ret变量,用来记录归并排序时,合并当前有序小数组后逆序对的个数。
②创建一个temp临时数组,其大小和合并当前两个有序小数组之后的数组大小相同。将原数组的元素拷贝到temp中。注意:temp数组索引和原数组索引有l个单位的偏移量。
③i是第一个有序小数组(左半区间)的起始索引,j是第二个有序小数组(右半区间)的起始索引,k表示当前处理到原数组的哪个位置。
若i>mid,说明左半区间已经全部处理完毕,将右半区间的所有值写回原数组;
若j>r,说明右半区间已经全部处理完毕,将左半区间的所有值写回原数组;
若temp[i - l] <= temp[j - l],说明左半区间元素<=右半区间元素,此时不构成逆序;
若temp[i - l] > temp[j - l],说明右半区间的j元素<左半区间的i之后的所有元素,此时就构成了逆序对,将左半区间i之后的所有元素个数记录到ret中,此时i和j对应的区间中逆序对的个数 = (mid - i + 1)个。
重复上述过程,直到整个数组有序。ret就是整个数组的逆序对个数。
注:就是在merge过程中,当右半区间元素<左半区间元素,才可能构成逆序对,其他情况就是普通的归并排序。(分治思想)
代码:
class Solution {
public int reversePairs(int[] nums) {
return reversePairsInternal(nums, 0, nums.length - 1);
}
/**
* 在nums[l...r]区间中,求逆序对的个数
* @param nums
* @param l
* @param r
* @return
*/
private int reversePairsInternal(int[] nums, int l, int r){
//终止条件
if(l >= r){
//此时数组只有一个元素,不存在逆序对
return 0;
}
//将数组不断拆分
int mid = (l + r) >> 1;
//递归分别求出左半区间和右半区间的逆序对个数
//左半区间和右半区间都是有序的
int leftNum = reversePairsInternal(nums, l, mid);
int rightNum = reversePairsInternal(nums, mid + 1, r);
if(nums[mid] > nums[mid + 1]){
//此时左半区间还有元素 > 右半区间,需要merge
//整个数组的逆序对个数 = 左区间个数 + 右区间个数 + 本次merge的逆序对个数
return leftNum + rightNum + merge(nums, l, mid, r);
}else{
//此时nums[mid] <= nums[mid + 1]
//说明此时整个数组已经有序,无需再merge
//整个数组的逆序对个数就是左区间个数 + 右区间个数
return leftNum + rightNum;
}
}
/**
* 归并排序的merge过程,返回当前合并后逆序对的个数
* @param nums
* @param l
* @param mid
* @param r
* @return
*/
private int merge(int[] nums, int l, int mid, int r){
int ret = 0;
int[] temp = new int[r - l + 1];
//将原数组的元素拷贝到temp
for (int i = l; i <= r; i++) {
temp[i - l] = nums[i];
}
//i是第一个有序小数组(左半区间)的起始索引
int i = l;
//j是第二个有序小数组(右半区间)的起始索引
int j = mid + 1;
//开始合并
//k表示当前处理到原数组的哪个位置
for (int k = l; k <= r; k++) {
if(i > mid){
//说明左半区间已经全部处理完毕,将右半区间的所有值写回原数组
nums[k] = temp[j - l];
j++;
}else if(j > r){
//说明右半区间已经全部处理完毕,将左半区间的所有值写回原数组
nums[k] = temp[i - l];
i++;
}else if(temp[i - l] <= temp[j - l]){
//左半区间元素 <= 右半区间元素,此时不构成逆序
nums[k] = temp[i - l];
i++;
}else{
//左半区间元素 > 右半区间元素,此时构成逆序
//此时i和j对应的区间中逆序对的个数 = (mid - i + 1)个。
ret += (mid - i + 1);
nums[k] = temp[j - l];
j++;
}
}
return ret;
}
}