数组中的逆序对
剑指offer51:https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/
题干:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
思路:
-
首先要明确升序数组就没有所谓的逆序对,降序数组就是任意抽取两个元素都构成逆序对,换句话说,数完逆序对,我把可以把数过的区间进行排序,后续在统计别的逆序对对数的时候就可以避免重复计算
-
逆序对有两种数法,一种是数这个数后面有多少个元素比自己小,那针对这个位置的逆序对的对数就是多少个,反过来,也可以数一个元素前面有多少个元素比自己大,那就针对该位置有多少个逆序对,之所以不同时数前面和后面的逆序对,是因为我们想在两个升序区间范围内去数,所以联想到归并排序.
-
归并排序的时候就是先拆分成两个区间,我们可以先对左区间进行逆序对的确定,然后将其升序排序,然后再把右半边区间进行逆序对的确定,然后再把右半边区间进行升序排序,之所以要升序排序,就是为了防止后续合并的时候发生逆序对的重复计算,当然除了两个子区间的逆序对数目的确定,两个子区间在合并的时候再统计整个的逆序对的个数,可以发现左右子区间虽然升序排序了,但是并不影响左右区间内的元素构成逆序对的个数情况.
-
如何在合并两个有序数组的时候统计逆序对的个数呢?看图:
之所以在研究两个有序数组的逆序对如何去统计,是因为我们根据递归函数的含义已经明确了左右子区间已经完成了逆序对的统计,统计完成各自已经有序,所以我们只需要处理好两个升序数组如何统计逆序对个数即可.(并且单个元素肯定有序…)
言归正传:统计上述两个有序数组的逆序对个数时,此处采用的是后半边区间内j遍历到的元素在前半边区间有几个元素比自己大,那j下标的元素就可以为逆序对总数贡献几.
比如原始数组依次被覆盖:1,然后i++,2和4比较要放2了,此时的4,5,8都是比自己大的,所以2的位置为计数器贡献3,可以观察发现这个3正是mid-i+1得来的.
-
所以经过上述论述可以写出如下代码:
class Solution {
public int reversePairs(int[] nums) {
if(nums==null||nums.length<2) return 0;
//临时数组,或者说是辅助数组
int[] temp = new int[nums.length];
return mergerSort(nums,0,nums.length-1,temp);
}
private int mergerSort(int[] nums, int left, int right,int[] temp) {
if(left==right) return 0;
int mid = left+(right-left)/2;
int leftPairs = mergerSort(nums,left,mid,temp);
int rightPairs = mergerSort(nums,mid+1,right,temp);
int crossPairs = merge(nums,left,mid,right,temp);
return leftPairs+rightPairs+crossPairs;
}
private int merge(int[] nums, int left, int mid, int right, int[] temp) {
for(int i = left;i<=right;i++){
temp[i]=nums[i];
}
int i = left;
int j = mid+1;
int count=0;
for(int k = left;k<=right;k++){
if(i==mid+1){
nums[k]=temp[j];
j++;
}else if(j==right+1){
nums[k]=temp[i];
i++;
} else if(temp[i]<=temp[j]){
nums[k]=temp[i];
i++;
}else{
nums[k]=temp[j];
j++;
count+=mid-i+1;
}
}
return count;
}
}