描述
解题思路:归并排序
分治:分治即“分而治之”,“分”指的是将一个大而复杂的问题划分成多个性质相同但是规模更小的子问题,子问题继续按照这样划分,直到问题可以被轻易解决;“治”指的是将子问题单独进行处理。经过分治后的子问题,需要将解进行合并才能得到原问题的解,因此整个分治过程经常用递归来实现。
具体做法:
1、这里要借助一个辅助数组,用来暂时存储合并后的结果。然后就开始进入划分阶段,把原数组从中间分开,直到子数组长度为1。
2、使用归并排序对原数组进行排序,并且统计逆序对,在这里设置两个指针i,j分别在左右子区间上移动,此时左区间的下标i都是小于右区间的,若知道了第一个大于a[j]的数,设为a[i],则左区间中a[i]以后的所有数,都比a[j]大。故此时,在左区间中,与a[j]构成逆序对的数字个数为左边剩下的数,剩余的数=(右端-左端+1)=(mid-i+1)。这个就是逆序对的计算方法。
3、将排好序的子序列合并,同时累加逆序对。
图解:
代码:
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @return int整型
*/
public int mod = 1000000007;
public int mergeSort(int left,int right,int [] data,int[] temp){
if(left>=right){
return 0;
}
int mid = (left+right)/2;
int res = mergeSort(left,mid,data,temp)+mergeSort(mid+1,right,data,temp);
res %= mod;
//i,j代表两个指针,分别在左右子区间上移动。
int i = left,j = mid+1;
for(int k=left;k<=right;k++){
temp[k] = data[k]; //temp为辅助数组
}
for(int k=left;k<=right;k++){
if(i==mid+1){ //如果左边有剩余,不太懂这处代码
data[k] = temp[j++];
}
//如果右边有剩余,或者左边数更小
else if(j==right+1||temp[i]<=temp[j]){
data[k] = temp[i++];//不太懂处代码
}
else{
data[k] = temp[j++];//逆序对计算方法
res += mid-i+1;
}
}
return res%mod;
}
public int InversePairs (int[] nums) {
// write code here
int n = nums.length;
int [] res = new int[n];
return mergeSort(0,n-1,nums,res);
}
}
个人疑问:不知道有没有和我一样的小伙伴,在看这道题的解题思路会有这样的疑问:为什么可以排序呢?这里题目要求的是在一个已经列好的数组中左边的数大于右边,才被称为逆序数,而如果使用归并排序的话,这个数组不是都有序了吗,有序的基础上找逆序,不是和题目违背了吗?
经过思考,我个人的见解是这样的,这里归并排序计算逆序对的数量和暴力解法不一样,暴力解法是在一个已有的数组中,对于每一个数,都判断其他的数是否比该数大,而递归排序,它比较的不是相邻的两个数,而是相邻的两个子数组,我认为这是看懂这道题的关键,因为比较的是两个子数组,所以在两个子数组中已经排好序是没关系的,因为两个子数组的相对顺序没有变,所以如果在左区间发现了一个比右区间大的数,那么说明左区间中这个数以后的数都会比右区间大,这是递归算法计算的公式。