在前面我们有对归并排序进行讲解,现在我们结合一个其中的典型例题讲解。
题目描述
给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。
问题分析
根据题目意思,也就是说当i <= j 时,出现了 a[i] > a[j]时,就说明出现了一个逆序对。
看到这个核心条件i <= j
和 q[i] > q[j]
貌似好像是和归并排序有点类似的。
我们来回忆一下归并排序的步骤:
1.平分区间
2.判断q[i] 和 q[j],若q[i] <= q[j],默认将i ++;否则将j++;(循环判断左半边i <= mid
和右半边j <=r
3.神龙摆尾,直接将数组后面的数移入即可
结合这个题目,我们发现我们只需要在否则
的处理里面,加上逆序对的计算接口。
进一步的,我们知道因为 归并排序能保证两个序列相对有序
也就是说,当q[i] > q[j]时,我们就可以直接将后面的数字个数添加进去
假设当前我们有以下序列
当left比较第一个时,第一个进入临时数据temp
然后进行i++,现在比较q[i] = 3 和 q[j] = 1
因为这里left序列后面都将会比1大,所以直接添加后面的个数——
mid - i + 1
上面考虑的是较为复杂的情况(逆序对跨序列)
情况二:全部在左侧
情况三:全部在右侧
代码模板
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 1e6 + 8;
int n;
int q[N] , temp[N];
ll merge_sort(int l , int r){
if(l >= r ) return 0;
int mid = l + r >> 1;
int i = l , j = mid + 1 , k = 0;
ll res = merge_sort(l , mid) + merge_sort(mid + 1 , r);
while(i <= mid && j <= r){
if(q[i] <= q[j]){
temp[k ++] = q[i ++ ];
}else{
temp[k ++ ] = q[j ++ ];
res += mid - i + 1;
}
}
while(i <= mid ) temp[k ++] = q[i++];
while(j <= r) temp[k ++ ] = q[j++];
for(int i = l , j = 0 ; i <= r ; i ++ , j ++){
q[i] = temp[j];
}
return res;
}
int main(){
cin >> n;
for(int i = 0 ; i < n ; i ++){
cin >> q[i];
}
cout << merge_sort(0 , n - 1);
return 0;
}
实现细节
long long
因为逆序对是不断叠加的,若原始数组为倒序序列。就可能会出现超出int
的情况。
所以结果需要设置long long
并且为了方便定义,使用
typedef
进行新的别名
初始值res
在前面的情况讨论中,我们知道会有三种情况(全在左边 | 全在右边 | 一个在左边,一个在右边)
所以,我们就可以初始化res为 全在左边的值 + 全在右边的值
神龙摆尾(处理末尾状态)
while(i <= mid ) temp[k ++] = q[i++];
while(j <= r) temp[k ++ ] = q[j++];