💁♂️个人主页:进击的荆棘
👇作者其它专栏:
相关题解
1.分治------快排
2.分治------归并
1.分治------快排
1.1颜色分类
算法思路:
类比数组分两块的算法思想,将数组分为三块,我们可以在添加一个指针,实现数组分三块。
设数组大小为n,定义三个指针left,cur,right:
●left:用来标记0序列的右端点,因此初始化为-1;
●cur:用来扫描数组,初始化为0;
●right:用来标记2序列的左端点,因此初始化为n。
在cur往后扫描的过程中,保证:
●0,left内元素都是0;
●left+1,cur-1内元素都是1;
●cur,right-1内元素是待扫描区域;
●right,n内的元素都是2.
算法流程:
a.初始化cur=0,left=-1,right=nums.size();
b.当cur<right的时候(因为right表示的是2序列的左边界,因此当cur碰到right的时候,说明数组已经扫描完毕),一直进行下面循环:
根据numscur的值,可以分为下面三种情况:
i.numscur==0;说明此时这个位置的元素需要在left+1的位置上,因此交换left+1与cur位置的元素,并且让left++(指向0序列的右边界),cur++(因为当前元素已扫描过,可以放心向后移动);
ii.numscur==1;说明这个位置应该在left和cur之间,此时无需交换,直接让cur++,判断下一个元素即可;
iii.numscur==2;说明这个位置的元素应该在right-1的位置,因此交换right-1与cur位置的元素,并且让right--(重新指向2序列的左边界),cur不变(因为交换后的元素未被扫描,需在下轮循环中确定)
c.当循环结束后:
0,left表示0序列;
left+1,right-1表示1序列;
right,nums.size()-1表示2序列。
cpp
class Solution {
public:
void sortColors(vector<int>& nums) {
//三指针,数组被分为4块
//left:0的最右侧 right:2的最左侧
//0~left:全为0 left+1~i-1:全为1 i~right-1:待扫描区域 right~n-1:全为2
int n=nums.size();
int left=-1,right=n,cur=0;
while(cur<right){
if(nums[cur]==0) swap(nums[++left],nums[cur++]);
else if(nums[cur]==1) cur++;
else swap(nums[--right],nums[cur]);
}
}
};
1.2排序数组
算法思路:
经过数据结构中快速排序的思想可以知道,快排最核心的一步就是Partition(分割数据):将数据按照一个标准,分成左右两部分。
若我们使用三路划分的思想,将数组分为左 中 右三部分:左边是比基准元素小的数据,中间是与基准元素相同的数据,右边是比基准元素大的数据。然后再去递归排序左边与右边即可。
再处理数据量中有很多重复数据的情况下,效率会大大提升。
算法流程(随机选择基准):
函数设计: int getRandom(vector<int>& nums,int left,int right)
a.再主函数中种一颗随机数种子;
b.在随机选择基准函数这里生成一个随机数;
c.由于我们要随机产生一个基准,依次可以将随机数转换成随机下标:让随机数%上区间大小,然后加上区间左边界,就可以得到下标。
快速排序算法主要流程:
a.定义递归出口;
b.利用随机选择基准函数生成一个基准元素;
c.利用三路划分思想将数组划分成三个区域;
d.递归处理左边区域和右边区域。
cpp
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
srand(time(NULL));//种下随机数种子
qsort(nums,0,nums.size()-1);
return nums;
}
void qsort(vector<int>& nums,int l,int r){
if(l>=r) return ;
//获取随机数
int key=getRandom(nums,l,r);
int left=l-1,right=r+1,cur=l;
while(cur<right){
if(nums[cur]<key) swap(nums[++left],nums[cur++]);
else if(nums[cur]==key) cur++;
else swap(nums[--right],nums[cur]);
}
//递归排序,[l,left] [left+1,right-1] [right,r]
qsort(nums,l,left);
qsort(nums,right,r);
}
int getRandom(vector<int>& nums,int l,int r){
int e=rand();
return nums[e%(r-l+1)+l];
}
};
1.3数组中的第 K 个最大元素
算法思路:
在快排中,当我们把数组【分成三块】之后:l,left left+1,right-1 right,r,我们可以计算每一个区间内元素的【个数】,进而推断我们要找的元素是在【哪一个区间】里面,。
然后直接去【相应的区间】中去寻找到结果即可。
cpp
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
return qsort(nums,0,nums.size()-1,k);
}
int qsort(vector<int>& nums,int l,int r,int k){
if(l==r) return nums[l];
//1.随机数取key
int key=getRandom(nums,l,r);
//2.三路划分
int left=l-1,right=r+1,cur=l;
while(cur<right){
if(nums[cur]<key) swap(nums[++left],nums[cur++]);
else if(nums[cur]==key) cur++;
else swap(nums[--right],nums[cur]);
}
//3.分情况讨论,看第k大落在哪个区间
//a区间:[l,left] b区间:[left+1,right-1] c区间:[right,r]
int c=r-right+1,b=right-1-(left+1)+1;
if(c>=k) return qsort(nums,right,r,k);
else if(b+c>=k) return key;
else return qsort(nums,l,left,k-b-c);
}
int getRandom(vector<int>& nums,int l,int r){
return nums[rand()%(r-l+1)+l];
}
};
1.4最小K个数
算法思路:
与 1.3数组中的第 K 个最大元素 类似。只不过一个是在大元素中的区间内寻找,一个在小元素的区间内寻找。
cpp
class Solution {
public:
vector<int> smallestK(vector<int>& arr, int k) {
srand(time(NULL));
qsort(arr,0,arr.size()-1,k);
return {arr.begin(),arr.begin()+k};
}
void qsort(vector<int>& arr,int l,int r,int k){
if(l>=r) return;
//1.随机数选key+三路划分
int key=getRandom(arr,l,r);
int left=l-1,right=r+1,cur=l;
while(cur<right){
if(arr[cur]<key) swap(arr[++left],arr[cur++]);
else if(arr[cur]==key) cur++;
else swap(arr[--right],arr[cur]);
}
//分情况讨论
//a:[l,left] b:[left+1,right-1] c:[right,r]
int a=left-l+1,b=right-1-(left+1)+1;
if(a>k) qsort(arr,l,left,k);
else if(a+b>=k) return;
else qsort(arr,right,r,k-a-b);
}
int getRandom(vector<int>& arr,int l,int r){
return arr[rand()%(r-l+1)+l];
}
};
2.分治------归并
2.1排序数组
算法思路:
归并排序的流程充分体现了【分而治之】的思想,大体分为两步:
●分:将数组一分为二,一直分解到数组的长度为1,使整个数组的排序过程被分为【左半部分排序】+【右半部分排序】;
●治:将两个较短的【有序数组合并成一个长的有序数组】,一直合并到最初的长度。
cpp
class Solution {
vector<int> tmp;
public:
vector<int> sortArray(vector<int>& nums) {
tmp.resize(nums.size());
mergeSort(nums,0,nums.size()-1);
return nums;
}
void mergeSort(vector<int>& nums,int left,int right){
if(left>=right) return ;
//选择中点将数组分为两块
int mid=(right+left)>>1;
mergeSort(nums,left,mid);
mergeSort(nums,mid+1,right);
//将数组排序
int cur1=left,cur2=mid+1,i=0;
while(cur1<=mid&&cur2<=right){
tmp[i++]=nums[cur1]<=nums[cur2]?nums[cur1++]:nums[cur2++];
}
while(cur1<=mid) tmp[i++]=nums[cur1++];
while(cur2<=right) tmp[i++]=nums[cur2++];
//定位到原数组中
for(int i=left;i<=right;i++)
nums[i]=tmp[i-left];
}
};
2.2交易逆序对的总数
算法思路:
用归并排序求逆序数是很经典的方法,主要就是在归并排序的合并过程中统计出逆序对的数量,也就是在合并两个有序序列的过程中,能够快速求出逆序对的数量。
我们将这个问题分解成几个小问题,逐一破解这道题。
注意:默认都是升序,若掌握升序的话,降序的归并过程也是可以解决的。
●先解决第一个问题,为什么可以利用归并排序?
若我们将数组从中间划分成两个部分,那么我们可以将逆序对产生的方式划分成三组:
●逆序对中中两个元素:全部从左数组中选择
●逆序对中两个元素:全部从右数组中选择
●逆序对中两个元素:一个选左数组另一个选右数组
三种情况下产生的逆序对的总和,就是总的逆序对数量。
因此,我们可以利用归并排序的过程,**先求出左半数组中逆序对的数量,再求出右半数组中逆序对的数量,最后求出一个选择左边,另一个选择右边情况下逆序对的数量,**三者相加即可。
●解决第二个问题,为什么要这样做?
再归并排序合并的过程中,我们得到的是两个有序的 数组。我们可以利用数组的有序性,快速统计出逆序对的数量,而不是将所有情况都枚举出来。
●最核心的问题,如何在合并两个有序数组的过程中,统计出逆序对的数量?
合并两个有序序列时求逆序对的方法有两种:
1.快速统计出某个数前面有多少个数比它大;
2.快速统计出某个数后面有多少个数比它小;
方法一:快速统计出某个数前面有多少个数比它大
通过一个示例来演示方法一:
假定已经有两个有序的序列以及辅助数组**left=5,7,9,right=4,5,8,help=\[\],**通过合并两个有序数组的过程,来求得逆序对的数量:
规定如下定义来叙述过程:
cur1 遍历left 数组,cur2 遍历right数组
ret记录逆序对的数量
第一轮循环:
leftcur1>rightcur2, 由于两个数组都是升序的,那么我们可以断定,此刻left 数组中**cur1,2** 区间内的3 个元素均可与right **cur2** 的元素构成逆序对,因此可以累加逆序对的数量ret+=3, 并且将right cur2 加入到辅助数组中,**cur2++**遍历下一个元素。
第一轮循环结束后:left=5,7,9,right=x,5,8,help4,ret=3,cur1=0,cur2=1
第二轮循环:
leftcur1==rightcur2, 因为rightcur2 可能与left 数组中往后的元素构成逆序对,因此我们需要将leftcur1 加入到辅助数组中,此时没有产生逆序对,不更新ret。
第二轮结束后:left=x,7,9,right=x,5,8,help=4,5,ret=3,cur1=1,cur2=1
第三轮循环:
leftcur1>rightcur2 ,与第一轮循环相同,此刻left 数组中**cur1,2** 区间内的2 个元素均可与rightcur2 的元素构成逆序对,更新ret 的值为ret+=2 ,并且将rightcur2 加入到辅助数组中去,**cur2++**遍历下一个元素。
第三轮循环结束后:left=x,7,9,right=x,x,8,help=4,5,5,ret=5,cur1=1,cur2=2
第四轮循环:
leftcur1<rightcur2 ,由于两个数组都是升序的,因此我们可以确定leftcur1 比right 数组中的所有元素都要小。leftcur1 这个元素是不可能与right 数组中的元素构成逆序对的。因此,大胆的将leftcur1 这个元素加入到辅助数组中,不更新ret的值。
第四轮循环结束后:left=x,x,9,right=x,x,8,help=4,5,5,7,ret=5,cur1=2,cur2=2
第五轮循环:
leftcur1>rightcur2,与第一、第三轮循环相同。此时left数组的1个元素能与rightcur2构成逆序对,更新ret的值,并且将rightcur2加入到辅助数组中。
第五轮循环结束后:left=x,x,9,right=x,x,x,help=4,5,5,7,8,ret=6,cur1=2,cur2=2
处理剩余元素:
●若是左边出现剩余,说明左边剩下的所有元素都是比右边元素大的,但是它们都是已经被计算过的(我们以右边的元素为基准的),因此不会产生逆序对,仅需归并即可。
●若是右边出现剩余,说明右边剩下的元素都是比左边大的,不符合逆序对的定义,因此也不需要处理,仅需归并排序即可。
整个过程只需将两个刷组遍历一遍即可,时间复杂度O(N)。
由上述过程我们可以得出方法一统计逆序对的关键字:
在合并有序数组 的时候,遇到左数组当前元素>右数组当前元素 时,我们可以通过计算左数组中剩余元素 的长度,就可以快速求出右数组当前元素前面有多少个数比它大,对比解法一种一个一个枚举逆序对效率快了许多。
方法二:快速统计出某个数后面有多少个数比它小
依旧通过一个示例来演示方法二:
假定已经有两个有序的序列以及辅助数组left=5,7,9,right=4,5,8,help=\[\],通过合并两个有序数组的过程,来求得逆序对的数量:
规定如下定义来叙述过程:
cur1 遍历left 数组,cur2 遍历right数组
ret记录逆序对的数量
第一轮循环:
leftcur1>rightcur2 ,先不要着急统计,因为我们要找的是当前元素后面有多少比它小的,这里虽然出现了一个,但是right 数组中依旧还可能有其余比它小的。因此仅将rightcur2 加入到辅助数组中去,并且cur2++。
第一轮循环结束后:left=5,7,9,right=x,5,8,help=4,ret=0,cur1=0,cur2=1
第二轮循环:
leftcur1==rightcur2 ,由于两个数组都是升序,这个时候对于元素leftcur1 来说,我们已经可以断定right 数组中**0,cur2)** 左闭右开区间上的元素都是比它小的。因此此时可以统计逆序对的数量**ret+=cur2-0** ,并且将**left\[cur1** 放在辅助数组中去,**cur1++**遍历下一个元素。
第二轮循环结束后:left=x,7,9,right=x,5,8,help=4,5,ret=1,cur1=1,cur2=1
第三轮循环:
leftcur1>rightcur2 ,与第一轮循环相同,直接将rightcur2 加入到辅助数组中,**cur2++**遍历下一个元素。
第三轮循环结束后:left=x,7,9,right=x,x,8,help=4,5,5,ret=1,cur1=1,cur2=2
第四轮循环:
leftcur1<rightcur2 ,由于两个数组都是升序的,这个时候对于元素leftcur1 来说,我们依旧极影可以断定right 数组中**0,cur2)** 左闭右开区间上的元素都是比它小的。因此此时可以统计逆序对的数量**ret+=cur2-0** ,并且将**left\[cur1** 放入辅助数组中去,**cur1++**遍历下一个元素。
第四轮循环结束后:left=9,right=8,help=4,5,5,7,ret=3,cur1=2,cur2=2
第五轮循环:
leftcur1>rightcur2 ,与第一、第三轮循环相同。直接将**rightcur2**加入到辅助数组中去,cur2++遍历下一个元素。
第五轮循环结束后:left=x,x,9,right=x,x,x,help=4,5,5,7,8,ret=3,cur1=2,cur2=2
处理剩余元素:
●若左边没有出现剩余,说明左边剩下的所有的元素都是比右边元素大的,但是相比较于方法一,逆序对的数量是没有统计过的。因此,我们需要统计ret的值:
●设左边数组剩余元素的个数为leave
●ret+=leave*(cur2-0)
对于本题来说,处理剩余元素的时候,left 数组剩余1 个元素,cur2-0=3 ,因此ret 需要累加上3 ,结果为6。与方法一求得的结果相同。
●若是右边出现剩余,说明右边剩下的元素都是比左边大的,不符合逆序对的定义,因此也不需要处理,仅需归并排序即可。
整个过程只需要将两个刷组遍历一遍即可,时间复杂度依旧为O(N)。
由上述过程我们可以得出方法二统计逆序对的关键点:
在合并有序数组 的时候,遇到左数组当前元素<=右数组当前元素 时,我们可以通过右数组已经遍历过的元素 的长度,快速求出左数组当前元素后面有多少个数比它大。
但是需要注意的是,在处理剩余元素的时候,方法二还需要统计逆序对的数量。
cpp
//升序
class Solution {
int tmp[50001];
public:
int reversePairs(vector<int>& record) {
return mergeSort(record,0,record.size()-1);
}
int mergeSort(vector<int>& record,int left,int right){
if(left>=right) return 0;
int ret=0;
//1.找中间点,将数组分为两部分
int mid=(left+right)>>1;
//2.左边部分个数+排序+右边部分+排序
ret+=mergeSort(record,left,mid);
ret+=mergeSort(record,mid+1,right);
//3.一左一右(升序)
int cur1=left,cur2=mid+1,i=0;
while(cur1<=mid&&cur2<=right){
if(record[cur1]<=record[cur2]) tmp[i++]=record[cur1++];
else{
ret+=mid-cur1+1;
tmp[i++]=record[cur2++];
}
}
//4.将左右排序
while(cur1<=mid) tmp[i++]=record[cur1++];
while(cur2<=right) tmp[i++]=record[cur2++];
for(int i=left;i<=right;i++)
record[i]=tmp[i-left];
return ret;
}
};
//降序
class Solution {
int tmp[50001];
public:
int reversePairs(vector<int>& record) {
return mergeSort(record,0,record.size()-1);
}
int mergeSort(vector<int>& record,int left,int right){
if(left>=right) return 0;
int ret=0;
//1.找中间点,将数组分为两部分
int mid=(left+right)>>1;
//2.左边部分个数+排序+右边部分+排序
ret+=mergeSort(record,left,mid);
ret+=mergeSort(record,mid+1,right);
//3.一左一右(降序)
int cur1=left,cur2=mid+1,i=0;
while(cur1<=mid&&cur2<=right){
if(record[cur1]<=record[cur2]) tmp[i++]=record[cur2++];
else{
ret+=right-cur2+1;
tmp[i++]=record[cur1++];
}
}
//4.将左右排序
while(cur1<=mid) tmp[i++]=record[cur1++];
while(cur2<=right) tmp[i++]=record[cur2++];
for(int i=left;i<=right;i++)
record[i]=tmp[i-left];
return ret;
}
};
2.3计算右侧小于当前元素的个数
算法思路:
这一道题的解法与 2.2交易逆序对的总数的解法是类似的,但是这一道题要求的不是求总的个数,而是要返回一个数组,记录每一个元素的右边有多少个元素比自己小。
但是在我们归并排序的过程中,元素的下标是会跟着变化的,因此我们需要一个辅助数组,来将数组元素和对应的下标绑定在一起归并,也就是再归并元素的时候,顺势将下标转移到对应的位置上。
由于我们要快速统计出某一个元素后面有多少个比它小的,因此我们可以利用求逆序对的第二种方法。
算法流程:
●创建两个全局的数组:
**vector<int> index:**记录下标
**vector<int> ret:**记录结果
index 用来与原数组中对应位置的元素绑定,ret用来记录每个位置统计出来的逆序对的个数
●**countSmaller()**主函数:
a.计算nums 数组的大小为n;
b.初始化定义的两个全局的数组;
i.为两个数组开辟大小为n的空间
ii.index初始化为数组下标
iii.ret初始化为0
c.调用mergeSort ()函数,并返回ret结果数组。
●**void mergeSort(vector<int>& nums,int left,int right)**函数:
函数设计:通过修改全局的数组ret,统计出每一个位置对应的逆序对的数量,并且排序;无需返回值,因为直接对全局变量修改,当函数结束时,全局变量已经被修改成最后的结果。
●mergeSort()函数流程:
a.定义递归出口:left>=right时,直接返回;
b.划分区间:根据中点mid ,将区间划分为**left,mid mid+1,right;**
c.统计左右两个区间逆序对的数量:
i.统计左边区间**left,mid** 中每个元素对应的逆序对的数量到ret数组中,并排序;
ii.统计右边区间**mid+1,right** 中每个元素对应的逆序对的数量到ret数组中,并排序。
d.合并左右两个有序区间,并且统计出逆序对的数量:
i.创建两个大小为right-left+1大小的辅助数组:
●**numsTmp:**排序用的辅助数组;
●**indexTmp:**处理下标用的辅助数组。
ii.初始化遍历数组的指针:cur1=left (遍历左半部分数组)cur2=mid+1 (遍历右半边数组)dest =0(遍历辅助数组)curRet(记录合并时产生的逆序对的数量);
iii.循环合并区间:
●当**numscur1<=numscur2**时:
▢说明此时**mid+1,cur2)** 之间的元素都是小于**nums \[cur1** 的,需要累加到ret 数组的indexcur1 位置上(因为index存储的是元素对应位置在原数组中的下标)
▢归并排序:不仅要将数据放到对应的位置上,也要将数据对应的下标也放在对应的位置上,是数据与原始的下标绑定在一起移动。
●当numscur1>numscur2 时,无需统计,直接归并,注意index也要跟着归并。
iv.处理归并排序中剩余的元素:
●当左边有剩余的时候,还需要统计逆序对的数量;
●当右边还有剩余的时候,无需统计,直接归并。
v.将辅助数组的内容替换到原数组中;
cpp
class Solution {
public:
vector<int> counts;
vector<int> index;//记录当前元素的原始下标
int tmpNum[100001];
int tmpIndex[100001];
vector<int> countSmaller(vector<int>& nums) {
int n=nums.size();
counts.resize(n);
index.resize(n);
//为它初始化
for(int i=0;i<n;i++)
index[i]=i;
mergeSort(nums,0,n-1);
return counts;
}
void mergeSort(vector<int>& nums,int left,int right){
if(left>=right) return ;
//将数组分为两块,分别处理
int mid=(left+right)>>1;
mergeSort(nums,left,mid);
mergeSort(nums,mid+1,right);
//将一左一右进行处理
int cur1=left,cur2=mid+1,i=0;
while(cur1<=mid&&cur2<=right){//降序
if(nums[cur1]<=nums[cur2]){
tmpNum[i]=nums[cur2];
tmpIndex[i++]=index[cur2++];
}
else{
//防止对原有个数覆盖
counts[index[cur1]]+=right-cur2+1;
tmpNum[i]=nums[cur1];
tmpIndex[i++]=index[cur1++];
}
}
//处理剩余元素
while(cur1<=mid){tmpNum[i]=nums[cur1];tmpIndex[i++]=index[cur1++];}
while(cur2<=right){tmpNum[i]=nums[cur2];tmpIndex[i++]=index[cur2++];}
for(int i=left;i<=right;i++){
nums[i]=tmpNum[i-left];
index[i]=tmpIndex[i-left];
}
}
};
2.4翻转对
本题与 2.2交易逆序对的总数 的定义大同小异,逆序对是前面的数要大于后面的数。而翻转对是前面的一个数要大于后面的某个数的两倍。因此,依然可以用归并排序的思想来解决这个问题。
算法思路:
大思路与求逆序对的思路一样,就是利用归并排序的思想,将求整个数组的翻转对的数量,转换成三部分:左半区间翻转对的数量,右半区间翻转对的数量,一左一右选择翻转对的数量。重点就是在合并区间过程中,如何计算出翻转对的数量。
与上个问题不同的是,上一道题我们可以一边合并一遍计算,但是这道题要求的是左边元素大于右边元素的两倍,若我们直接合并的话,是无法快速计算出翻转对的数量的。
例如left=4,5,6 right=3,4,5时 ,若是归并排序的话,我们需要计算left 数组中有多少个能与3 组成翻转对。但是我们要遍历到最后一个元素6才能确定,时间复杂度较高。
因此我们需要在归并排序之前完成翻转对的统计。
下面依旧以一个示例来模仿两个有序序列如何快速求出翻转对的过程:
假定已经有两个有序的序列left=4,5,6 right=1,2,3。
假定已经有两个有序的序列left=4,5,6 right=1,2,3。
用两个指针cur1 cur2遍历两个数组。
● 对于任意给定的leftcur1 而言,我们不断地向右移动cur2 ,直到leftcur1<=2 *rightcur2 。此时对于right 数组而言,cur2 之前的元素全部都可以与**leftcur1**构成翻转对。
● 随后,我们再将cur1 向右移动一个单位,此时cur2 指针并不需要回退(因为left 数组是升序的)依旧往右移动直到leftcur1<=2*rightcur2。不断重复这样的过程,就能够求出所有左右端点分别位于两个子数组的翻转对数目。
由于两个指针最后都是不回退 的扫描到数组的结尾,因此两个有序序列求出翻转对的时间复杂度是O(N)。
综上所述,我们可以利用归并排序的过程,将求一个数组的翻转对转换成求左数组的翻转对数量+右数组中翻转对的数量+左右数组合并时翻转对的数量。
cpp
//降序
class Solution {
public:
int tmp[50001];
int reversePairs(vector<int>& nums) {
return mergeSort(nums,0,nums.size()-1);
}
int mergeSort(vector<int>& nums,int left,int right){
if(left>=right) return 0;
int ret=0;
//分成左右两块
int mid=(left+right)>>1;
ret+=mergeSort(nums,left,mid);
ret+=mergeSort(nums,mid+1,right);
//一左一右进行比较
int cur1=left,cur2=mid+1,i=0;
while(cur1<=mid){//降序
//防止溢出,用除法
while(cur2<=right&&nums[cur2]>=nums[cur1]/2.0) cur2++;
//这段区间无反转对
if(cur2>right) break;
ret+=right-cur2+1;
cur1++;
}
//将这段元素排序
cur1=left,cur2=mid+1;
while(cur1<=mid&&cur2<=right){
tmp[i++]=nums[cur1]<=nums[cur2]?nums[cur2++]:nums[cur1++];
}
while(cur1<=mid) tmp[i++]=nums[cur1++];
while(cur2<=right) tmp[i++]=nums[cur2++];
//还原到数组中
for(int i=left;i<=right;i++)
nums[i]=tmp[i-left];
return ret;
}
};
//升序
class Solution {
public:
int tmp[50001];
int reversePairs(vector<int>& nums) {
return mergeSort(nums,0,nums.size()-1);
}
int mergeSort(vector<int>& nums,int left,int right){
if(left>=right) return 0;
int ret=0;
//分成左右两块
int mid=(left+right)>>1;
ret+=mergeSort(nums,left,mid);
ret+=mergeSort(nums,mid+1,right);
//一左一右进行比较
int cur1=left,cur2=mid+1,i=0;
while(cur2<=right){//升序
while(cur1<=mid&&nums[cur2]>=nums[cur1]/2.0) cur1++;
//这段区间无反转对
if(cur1>mid) break;
ret+=mid-cur1+1;
cur2++;
}
//将这段元素排序
cur1=left,cur2=mid+1;
while(cur1<=mid&&cur2<=right){
tmp[i++]=nums[cur1]<=nums[cur2]?nums[cur1++]:nums[cur2++];
}
while(cur1<=mid) tmp[i++]=nums[cur1++];
while(cur2<=right) tmp[i++]=nums[cur2++];
//还原到数组中
for(int i=left;i<=right;i++)
nums[i]=tmp[i-left];
return ret;
}
};