在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
-
分治思想
分治就是分而之,面对一个规模复杂的问题,把它分解成一系列的简单子问题,对子问题求解的结果进行合并从而实现对整个问题的求解。
通过不断的递归,每次尽可能的缩小问题的规模,直至满足基线条件;基线条件必须尽可能的简单,最好能直接得到需要结果。比如对一个数组排序,基线条件就是当数组只有一个元素的时候,就认为是有序的,直接返回结果。
快速排序、归并排序都是采用的分治以及递归的思想。 -
归并排序
将两个有序的数组合并成一个有序数组称为归并。归并排序包含了两个过程:
①:从上往下的分解:把当前区间一分为二,直至分解为若干个长度为1的子数组
②:从下往上的合并:两个有序的子区域两两向上合并
如下图所示:
归并排序的一般步骤:
①.分解:把当前区间一分为二,分解点即中间点mid = (left+right)/2
②.求解:分别递归左右两个子区间[left…mid] 和 [mid+1…right]进行归并排序。递归的终结条件是子区间长度为1。
③.合并:把两个有序子数组合并需要占用一个临时空间,依次挪动两个子区间的指针,比较元素值大小,将较小的值存入临时空间的开头。将两个有序区间归并成一个临时有序区间[left…right],并将结果拷贝到原数组的区间[left…right],使原始数组[left…right]变为有序。 -
题目分析
本题是典型的归并排序例子。若左边元素值大于右边元素值则存在逆序对,逆序对是归并排序的附加结果。
左右两个子区间都是有序的,若左边区间中元素nums[i]>nums[j]则存在逆序对,因此左边区间中子区间[i,mid]的所有元素都和nums[j]构成了逆序对,减少了不必须要的重复计算。 -
代码示例
class Solution {
public:
int resault = 0;//全局变量,用于保存结果
int reversePairs(vector<int>& nums) {
vector<int> temp = vector(nums.size(),0);//创建临时数组,减少每次创建的开销
mergeSort(nums,temp,0,nums.size()-1);//归并排序
return resault;
}
void mergeSort(vector<int>& nums,vector<int>& temp,int left ,int right)
{
if( left >= right) return;//满足基线条件,直接返回
int mid = left+(right-left)/2;//分隔点
mergeSort(nums,temp,left,mid);//对左区间进行归并排序
mergeSort(nums,temp,mid+1,right);//对右区间进行归并排序
int i = left;//左区间指针
int j = mid+1;//右区间指针
int t = 0;//临时数组指针
while( i <= mid && j <= right)
{
if( nums[i] > nums[j] )//若左区间中元素值大于右区间中元素值,则存在逆序对
{
temp[t++] = nums[j++];
resault += (mid - i + 1);//仅在并归排序中增加一行代码,全局变量保存逆序对数量
}
else
{
temp[t++] = nums[i++];
}
}
while(i<= mid)//将左边剩余元素填充进temp中
{
temp[t++] = nums[i++];
}
while(j <= right)//将右序列剩余元素填充进temp中
{
temp[t++] = nums[j++];
}
t= 0;
//将temp中的元素全部拷贝到原数组[left,right]中
while(left <= right)
{
nums[left++] = temp[t++];
}
}
};