逆序对 归并排序求法 分治思想

逆序对介绍

如果存在正整数 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函数】进行归并操作

这里你可能会有很多问号:

排序之后对不是顺序被打乱了吗?

打乱顺序(排序)之前,我们已经求得了两边子数组的解的个数
而且求解这一趟的横跨的逆序对的时候,左右都排序了,但是他们相对位置不变,即左元素一定在左边子数组,右边同理

int unseq(vector<int> &nums, int l, int r)
{
	if(l>=r || l<0 || r>=nums.size()) return 0;
	int mid=(l+r)/2, cnt=0, i=mid;
	int lcnt = unseq(nums, l, mid);
	int rcnt = unseq(nums, mid+1, r); 
	for(int j=r; j>mid; j--)
	{
		while(i>=l && nums[j]<nums[i]) i--;
		cnt += mid-i;
	}
	inplace_merge(nums.begin()+l, nums.begin()+mid+1, nums.begin()+r+1);
	return lcnt+rcnt+cnt;
}

完整代码

#include <bits/stdc++.h>

using namespace std;

int unseq(vector<int> &nums, int l, int r)
{
	if(l>=r || l<0 || r>=nums.size()) return 0;
	int mid=(l+r)/2, cnt=0, i=mid;
	int lcnt = unseq(nums, l, mid);
	int rcnt = unseq(nums, mid+1, r); 
	for(int j=r; j>mid; j--)
	{
		while(i>=l && nums[j]<nums[i]) i--;
		cnt += mid-i;
	}
	inplace_merge(nums.begin()+l, nums.begin()+mid+1, nums.begin()+r+1);
	return lcnt+rcnt+cnt;
}

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;
}

int main()
{	
	vector<int> nums{2,7,4,3,1,8,6,5};
	cout<<unseq(nums)<<endl;
	cout<<unseq(nums, 0, nums.size()-1)<<endl;
	for(int i=0; i<nums.size(); i++) cout<<nums[i]<<" ";
	cout<<endl;
	
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章