官方解法带视频讲解,推荐先看视频再来看本文的讲解
https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/shu-zu-zhong-de-ni-xu-dui-by-leetcode-solution/
采用归并思想的分治法
可以发现在逆序数组中如5-4-3-2-1中的逆序对为4+3+2+1
计算过程主要是在不同阶段的情况下不断计算,归并方法刚好是在不同的区间范围内进行的再排序,所以我们可以考虑归并方法
核心思想其实是将所有的数字不断二分,直到所有的长度均为1的大小的数组,然后逐渐向上进行归并,注意这里的逻辑是先进后出的思想。
由于两个有序的数组(A、B)进行归并的时候,分别从头开始向后遍历,将较小的放入数组中,且如果存在逆序就加上相应的逆序数值;
逆序数值的计算方法是如果A>B可以构成逆序,在将B放入数组的同时看B前面一共有多少个数字,数量即为此时的逆序数值,类似上面例子中的4+3+2+1中的3等。
注意
当两个较大的int数字求平均的时候容易溢出,所以用left+(right-left)/2的方法进行计算平均值。
count的计算依赖于计算索引间距。
循环以及函数边界的传递多为闭区间,尤其在循环的时候需要注意。
时空复杂度
- 时间复杂度:O(NlogN)归并排序的时间复杂度
- 空间复杂度:O(N)归并排序需要用到的临时数组,这里可能会有疑惑,为什么我们不是以排序为目的进行,能不能只要count计数逆序对,而不顾排序结果呢?答案是不能,因为我们的count的计算依赖于cross函数,也就是归并的过程,而下一次的归并也就是count的计算是有前提的也就是要求是两个有序的子数组,所以排序过程至关重要。
public class Solution { public int reversePairs(int[] nums) { int len=nums.length; if(len<2){//如果只有0个或1个说明不存在 return 0; } int []copy=new int[len]; for(int i=0;i<len;i++){ copy[i]=nums[i]; } int[] tmp=new int[len]; return reversePairs(nums,0,len-1,tmp); } public int reversePairs(int[] nums,int left,int right,int[]tmp){ if(left==right){ return 0; } int mid=left+(right-left)/2; int leftnum=reversePairs( nums, left,mid,tmp); int rightnum=reversePairs(nums, mid+1,right,tmp); if(nums[mid]<=nums[mid+1]){//此时crossnum=0 return leftnum+rightnum; } int crossnum=mergenum(nums, left,mid,right,tmp); return rightnum+leftnum+crossnum; } public int mergenum(int[] nums,int left,int mid,int right,int[]tmp){ for(int i=left;i<=right;i++){ tmp[i]=nums[i]; } int i=left,j=mid+1; int count=0; for(int k=left;k<=right;k++){ if(i==mid+1){//表示左边的遍历到末尾了 nums[k]=tmp[j]; j++; }else if (j==right+1){//表示右边的遍历到末尾了 nums[k]=tmp[i]; i++; }else if(tmp[i]<=tmp[j]){ nums[k]=tmp[i]; i++; }else{ nums[k]=tmp[j]; j++; count+=(mid-i+1);//这里需要注意不是自增1,而是要计算索引间距 } } return count; } }