归并排序思想应用之逆序数对
归并排序是一种基本的排序,简洁的归并排序写出来是最基本的要求, 要做到一遍可以运行不出错也是很不容易,需要天天复习,阿里的面试当中让写一道程序就是关于归并排序的.很遗憾由于当时太紧张写了选择排序,脑子坏掉了感觉.
更加重要的是,归并排序是分而治之的思想.这种思想才是解题的根源.
剑指offer那道逆序对的题目做出来其实不容易.看一下这道题目的分析吧.
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
题目分析
基本思想是分而治之(有位牛客朋友说的特别好,思想是分而治之而不是归并排序,这才是真谛),具体体现类似归并排序,一个数组分成A B两段, 先对A和B分别求逆序对c1 ,c2,然后合并两个有序数组得到逆序對c3, 三者的和就是最终的结果.最关键的地方在于合并的过程.
我们由两个数组从后往前遍历进行归并,当遇到A[i] > B[i]的时候,B[i]及其前边有几个数,当前A[i]久产生几个逆序對.其余的情况(A[i]<=B[i]或两者之一先到达数组头)均不产生逆序数. 想到以A[i]为参照元素来找逆序对这点很重要. 写程序才能简洁.
另外一方面是数据量较大需要取模,这时候的索引要注意,最好选用long long类型比较保险.
代码
class Solution {
public:
long long InversePairs(vector<int > &data) {
return mergeSort(data , 0 , data.size()-1)%1000000007;
}
long long Mmerge(vector<int> &a , long long i ,long long mid , long long j ){
vector<int> res;
long long cc = 0;
long long si = mid, sj = j;
while(si >= i && sj >= mid+1){
if( a[si] > a[sj]){
res.push_back(a[si]);
cc=(cc - mid+ sj)%1000000007;
si--;
}
else{
res.push_back(a[sj]);
sj--;
}
}
while(si >= i)
{
res.push_back(a[si]);
si--;
}
while(sj >= mid+1)
{
res.push_back(a[sj]);
sj--;
}
for(long long ssi = i ; ssi <=j ; ssi++ ){
a[ssi] = res[j-ssi];
}
return cc;
}
long long mergeSort(vector<int> &a , int i , int j ){
if(i >= j){
return 0;
}
long long mid = (i + j)/2;
// 左闭右开
long long cc1 =mergeSort(a, i , mid);
long long cc2 =mergeSort(a, mid+1 , j);
long long cc =Mmerge(a, i , mid , j);
return (cc+cc1)%1000000007+cc2%1000000007;
}
};