逆序对介绍
如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则<A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。
求解一个数组中逆序对个数
其实就是求下标小的大数,那么二话不说先来暴力,暴力显然是O(n2),8太行
int unseq(vector<int> &nums)
{
int cnt = 0;
for(int i=0; i<nums.size(); i++)
for(int j=i+1; j<nums.size(); j++)
if(nums[i]>nums[j]) cnt++;
return cnt;
}
分治思想:借助归并排序
对于数组中的逆序对个数,将数组分为[l, mid]
与[mid+1, r]
区间,问题可以分解为子问题:
- 子数组
[l, mid]
的逆序对个数 lcnt - 子数组
[mid+1, r]
的逆序对个数 rcnt - A[i]位于左边,A[j]位于右边 且 A[i] > A[j] 的 <i, j> 组成的对
问题 1,2可以递归地求解,那么关键是如何求问题3,即横跨左右的对
注意这里求解问题12的时候,不单单只是求解,还对数组排序了,也就是左边/右边都是有序的状态,那么我们可以用O(n)的时间,完成求解
- 在右侧子数组中,从后往前枚举 j 下标
- 在左侧子数组中,设置指针 i ,一开始 i 指向mid
- i 向左滑动,找到第一个 i 使得 A[i]<=A[j]
- 说明前面
i+1 ~ mid
下标,A[i] 都大于 A[j] - 那么对于 A[j] 为右边的逆序对,找到了
mid-i
对
因为两边都有序,那么对于 j 下标,因为A[j]是递减的,那么A[j+1]配对的个数,同样可以运用到A[j]
上,此外还要额外判断A[j]是否能满足更多逆序对
这里利用两边数组的有序性,变相利用前面的结果,达到节省时间
看似两重循环,其实 i,j 回退不超过 n/2 次,所以复杂度O(n)
用O(n)时间找打横跨的逆序对个数,我们还要将数组归并,方便上一层递归继续查找
这里使用【inplace_merge函数】进行归并操作