小黄的刷题之路(五)——码题集OJ赛-逆序数


⭐感谢您能点击进来,这是对我莫大的鼓励⭐

1. 题目

在这里插入图片描述

2. 分析思路

2.1 审题和分析

看完题目,我们可以总结出以下重要的几点:

  • 左移实际上是循环左移
  • 序列有n个元素,而这n个元素恰好是 0到 n − 1 n-1 n1的排列
  • 最关键的是求解一个序列P的逆序数对个数
2.2 什么是逆序对个数

逆序对个数即逆序数。在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序对。一个排列中逆序对的总数就称为这个排列的逆序数。

说白了,逆序数就是跟标准序列(从小到大)相反序数的总和。如2431中,21,43,41,31是逆序,逆序数就是4。

2.2 思路
逆序数求法1——一个一个数

中学的时候我们计算逆序数,最简单也是最容易想到的方法就是,对于数列中的每一个数 a [ i ] a[i] a[i],遍历数列中的数 a [ j ] a[j] a[j] (其中 j<i),若 a [ i ] < a [ j ] a[i]<a[j] a[i]<a[j],则逆序数加1,对于每一个 a [ i ] a[i] a[i],看它的前面有几个数是比它大的,以此统计出来逆序数。可以看出这种求法有待优化,时间复杂度比较高

逆序数求法2——归并思想

使用归并排序求逆序数

归并排序是将数组 A[left , right] 分成两半 A[left , mid] 和 A[mid+1 , right]分别进行归并排序,然后再将这两半合并起来。

在合并的过程中(设left<=i<=mid,mid+1<=j<=right),当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,在前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上 mid + 1 - i 。因此,可以在归并排序中的合并过程中计算逆序数

归并排序时,每一次后方元素移到前面来的移动距离就是本次操作/本次递归函数的逆序数。这样累加每次递归过程的逆序数,在完成整个递归过程之后,最后的累加和就是逆序的总数

//===========归并排序=============
#include<iostream>
using namespace std;
int count=0;//计算逆序数
void Merge(int* A,int left,int mid,int right,int* tmp)
{	//Merge可以将两个有序的数组排好序,时间复杂度:o(n)
	int i=left;//左边部分索引
	int j=mid+1;//右边部分索引
	int k=left;//tmp数组索引
	while(i <= mid && j <= right)
	{
		if(A[i] <= A[j])tmp[k++]=A[i++];//左边比右边小
		else{//右边比左边小
			tmp[k++] = A[j++];
			count += mid-i+1;//只加这一句就能求逆序数了
		}
	}
	while(i <= mid)tmp[k++] = A[i++];//处理剩下的
	while(j <= right)tmp[k++] = A[j++];//处理剩下的
	//tmp[]已经有序,将tmp[]中数据复制回原数组A[]
	for(int i=left;i <= right;++i)A[i] = tmp[i];
}	
void MergeSort(int* A,int left,int right,int* tmp)//假定MergeSort能将一个乱序数组A排好序.
{
	if(left < right)//数组被分割到只剩一个元素时,不会进入这个循环,函数直接返回	
	{
		int mid=(left+right)/2;
		MergeSort(A,left,mid,tmp);//排好一个左边的数组1
		MergeSort(A,mid+1,right,tmp);//排好一个右边的数组2
		Merge(A,left,mid,right,tmp); //合并两个有序的数组
	}
}
 
void main()
{
	int A[]={2,1,3,6,4,0,11,3,5};
	int len=sizeof(A)/sizeof(A[0]);
	int *tmp=new int[len];
	MergeSort(A,0,len-1,tmp);
    cout<<"排序完的数组A:";
	for(int i=0;i<len;++i)cout<<A[i]<<' ';
	cout<<endl;
	cout<<"逆序数:"<<count<<endl;
	delete[] tmp;
}

上面的代码实现了归并排序且求逆序数,把其应用到题目中来。


循环左移对逆序数的改变

左移第一次,把 a [ 0 ] a[0] a[0]放到末尾,其余部分往前一个位置,得到新的序列b。再次提醒一下题目中很重要的一点:序列有n个元素,而这n个元素恰好是 0到 n − 1 n-1 n1的排列,所以其实序列的数字本身就隐含着自己该往前移动的距离

举个例子:a = [2 , 3 , 1 , 0],左移一次得到的新序列b = [3 , 1 , 0 , 2],绿色部分之间的相对位置不改变,所以a,b序列逆序数的变化我们只需要考虑这个被左移的2。

  • 本来有两个比2小的数(0和1)该被移到2前面,但现在不用了,它们只需要移动紧靠2后面的位置,整体少移了2个距离;而在最后面的2又需要往前移动一定距离,由于是0到 n − 1 n-1 n1的排列,所以整体多移 ( n − 1 ) − 2 (n-1)-2 (n1)2的距离,所以从序列a到序列b,逆序数的变化就是 [ ( n − 1 ) − 2 ] − 2 = − 1 [(n-1)-2]-2 = -1 [(n1)2]2=1 ,左移一次使上面的序列a的逆序数少了1。
  • 左移一次的逆序数 = 左移前的逆序数 + ( 整体多移的距离 ) − ( 整体少移的距离 ) +(整体多移的距离)-(整体少移的距离) +(整体多移的距离)(整体少移的距离)
for (i = 0; i < n - 1; i++)
{
	ans += (n - 1 - arrcp[i]) - arrcp[i];
	cout << ans << endl;
}

3. 代码实现

3.1 C++代码实现
#include<iostream>
using namespace std;
long mergesort(int left, int right, int* arr, int* temp) {
    if (left >= right)return 0;
    int mid = (left + right) / 2;
    long ans = mergesort(left, mid, arr, temp) + mergesort(mid + 1, right, arr, temp);
    int i = left, j = mid + 1;//左边部分索引,右边部分索引
    int k = 0;//tmp数组索引
    while (i <= mid && j <= right) {
        if (arr[i] <= arr[j])temp[k++] = arr[i++];//不产生逆序数
        else {//产生逆序数,是arr[j]放到arr[i]前面所需要移动的距离
            ans += mid - i + 1;
            temp[k++] = arr[j++];
        }
    }
    while (i <= mid)temp[k++] = arr[i++];
    while (j <= right)temp[k++] = arr[j++];
    //temp[]已经有序,将temp[]中数据复制回原数组arr[]
    for (i = left, j = 0; i <= right; i++, j++) {
        arr[i] = temp[j];
    }
    return ans;
}

int main()
{
    int n, i, j;
    long ans;
    cin >> n;
    int* arr = new int[n], * arrcp = new int[n], * temp = new int[n];
    for (i = 0; i < n; i++) {
        cin >> arr[i];
        arrcp[i] = arr[i];
    }
    ans = mergesort(0, n - 1, arr, temp);
    //此时arr已经排好序,arrcp是排序前的
    cout << ans << endl;
    for (i = 0; i < n - 1; i++) {
        ans += (n - 1 - arrcp[i]) - arrcp[i];
        cout << ans << endl;
    }
    delete[] temp;
    delete[] arr;
    delete[] arrcp;
    return 0;
}
3.2 总结

这道题的关键点在于

  • 利用归并排序求解逆序数,只需要在归并排序的基础上再加一句话
  • 一定注意所给的序列的特殊性,它是一个0到n-1的排列,隐含着元素该移动的距离

⭐感谢您能看到这里,我会好好努力继续分享好的文章的!⭐

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值