题目链接地址:
题目描述:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
输入:
每个测试案例包括两行:
第一行包含一个整数n,表示数组中的元素个数。其中1 <= n <= 10^5。
第二行包含n个整数,每个数组均为int类型。
输出:
对应每个测试案例,输出一个整数,表示数组中的逆序对的总数。
样例输入:
4
7 5 6 4
样例输出:
5
解题思路:
要求数组中的逆序对,我最开始想到的就是暴力法:简单来说,就是将数组中的第1个元素与数组中后面的元素进行比较,统计出比元素1小的逆序对数目,再将数组中的第2个元素与数组中后面的元素进行比较,统计出比元素2小的逆序对数目,依次类推,就能找出数组中所有的逆序对了。这种算法和冒泡排序很相似,只是没有交换数组元素的操作,时间复杂度是O(n^2)。一提交发现超时了。。。囧。后来上论坛看了一下此题的答疑,有大牛提出了归并的思路,于是便采用此思路想出了用归并排序来统计数组中逆序对数目的算法。后来仔细一想,采用归并排序算法可以减少一些不必要的比较操作,从而提高算法的效率。
举个栗子,以测试用例[7,5,6,4 ]为例,采用二路归并排序统计逆序对数目的过程如下:
(1) 执行第一趟归并排序,归并后生成的有序子数组长度为2,在此趟排序过程中可以找到逆序对(7,5)和(6,4),
此趟排序完后,数组变为[5,7,4,6];
(2) 执行第二趟归并排序,将子数组[5,7]和[4,6]合并成长度为4的数组,合并过程如下:
1)先比较5和4,因为5 > 4,所以可以认为有序子数组[5,7]中排在5之后的元素也肯定比4大,这样就不需要再将[5,7]中
排在5之后的元素与4进行比较也能找出(5,4)和(7,4)这两个逆序对;
2)将4并入到新的有序数组中,比较5和6,因为5 < 6,所以直接将5并入到有序数组中;
3)接下来再比较7和6,因为7 > 6,而子数组[5,7]中只有7还没有被并入到新的有序数组中,所以只找到(7,6)这个逆序对;
4)将6并入到新的有序数组中,此时有序子数组[4,6]已全部并入到新的有序数组中,接下来需要将有序子数组[5,7]中的7
也并入到新的有序数组中;
5)此时新的有序数组是[4,5,6,7],第二趟归并排序执行完毕;
(3) 经过两趟归并排序后,数组中所有的元素均呈现有序状态,一共找到5个逆序对。
需要注意的是:因为数组最大为100000,所以逆序对最多有(100000 * 100000)/2,而这个数已经超出了int的表示范围,
因此需要用long long型来存储逆序对的数目。
AC代码如下:(下面的代码用C提交报Runtime Error,我也不知道为什么 ╮(╯▽╰)╭)
#include<stdio.h>
#define MAX 100001
int number[MAX]; // 整数数组
int temp[MAX]; // 合并过程中临时保存整数数组元素的数组
/**
* 输入数组
* @param n 数组的元素个数
* @return void
*/
void inputArray(int n)
{
int i;
for(i = 0;i < n;i++)
scanf("%d",&number[i]);
}
/**
* 进行合并操作
* @param gap 合并操作后生成的有序数组的长度
* @param length 数组的总长度
* @return count 返回一趟归并排序过程中所统计到的逆序对的数目
*/
long long merge(int gap,int length)
{
long long count = 0; // 用于统计逆序对的数目
int t = 0; // 临时数组的下标
int begin,middle,end; // 将[begin,middle) 和 [middle,end)两个有序数组进行合并操作,合并后生成有序数组[begin,end)
int i,j,s;
begin = 0;
while(begin < length) // 如果begin >= length,则表示一趟归并排序完毕
{
/*当(begin + gap) < length,也就是最后两组待排序列长度之和小于gap时,end = length;*/
end = (begin + gap) < length?(begin + gap):length;
/*注意:因为经过之前的归并排序后,数组[begin,begin + gap / 2)和数组[begin + gap / 2,end)一定都是有序数组,
所以middle = begin + gap / 2,不是middle = (begin + end)/2;
因为当(begin + gap) > length时,end = length,此时(begin + end)/2 != (begin + begin + gap)/2;
所以无法保证数组[begin,(begin + end) / 2)和数组[(begin + end) / 2,end)都是有序数组。*/
middle = begin + gap / 2;
i = begin; // i是数组[begin,middle)中的指针
j = middle; // j是数组[middle,end)中的指针
while(i < middle && j < end)
{
if(number[i] > number[j])
{
temp[t++] = number[j++];
count += middle - i; //如果左边数组中还有剩余元素,则剩余数组number[i,middle)中的所有元素都大于number[j]
}
else
{
temp[t++] = number[i++];
}
}
/*将[begin,middle)中的剩余元素拷入到temp数组中*/
while(i < middle)
{
temp[t++] = number[i++];
}
/*将[middle,end)中的剩余元素拷入到temp数组中*/
while(j < end)
{
temp[t++] = number[j++];
}
begin += gap; // 对[begin,end) 数组排好序后,紧接着对[begin + gap,end + gap)数组进行排序
}
/* 一趟归并排序完成后,将temp数组拷回到整数数组number中 */
for(s = 0;s < length;s++)
{
number[s] = temp[s];
}
return count;
}
/**
* 采用归并排序统计长度为n的数组中的逆序对的个数
* @param n 数组的长度
* @return count 数组中的逆序对个数
*/
long long countReversePair(int n)
{
long long count = 0;
int gap = 2;
while(1)
{
if(0 == (n * 2) / gap)
break;
count += merge(gap,n);
gap = 2 * gap;
}
return count;
}
int main()
{
int n;
/*用于统计逆序对的个数,注意因为数组最大为100000,所以逆序对最多有(100000 * 100000)/2,
而这个数已经超出了int的表示范围,所以这里count要定义为long long类型*/
long long count;
while(EOF != scanf("%d",&n))
{
inputArray(n);
count = countReversePair(n);
printf("%lld\n",count);
}
return 0;
}
/**************************************************************
Problem: 1348
User: blueshell
Language: C++
Result: Accepted
Time:80 ms
Memory:1800 kb
****************************************************************/