剑指offer 面试题36:数组中的逆序对


题目:在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。例如,有一个数组为Array[0..n] 其中有元素a[i],a[j].如果 当i<j时,a[i]>a[j],那么我们就称(a[i],a[j])为一个逆序对。在数组{7,5,6,4}中一共存在5对逆序对,分别是(7,6),(7,5),(7,4),(6,4),(5,4)。

参考文献

排序算法汇总->归并排序

解题思路

看到这样的题目,最简单的想法就是遍历每一个元素,让其与后面的元素对比,如果大于则count++,但是这样的时间复杂度是o(n2)。这题有更好的解决方法,时间复杂度只需要o(nlogn)。其实这道题目的思路跟归并排序差不多,求逆序对的过程就是一个求归并排序的过程,在求出逆序对以后,原数组变得有序,是通过归并排序得到的。

(1)总体的意思就是将数组分成两段,首先求段内的逆序对数量,比如下面两段代码就是求左右两端数组段内的逆序对数量

inversions+=InversePairsCore(arry,start,mid,temp);//找左半段的逆序对数目
inversions+=InversePairsCore(arry,mid+1,end,temp);//找右半段的逆序对数目

(2)然后求段间的逆序对数量,如下面的代码

inversions+=MergeArray(arry,start,mid,end,temp);//在找完左右半段逆序对以后两段数组有序,然后找两段之间的逆序对。最小的逆序段只有一个元素。

(3)然后在求段间逆序对的时候,我们分为arry[start...mid]和arry[mid+1...end],然后设置两个指针ij分别指向两段数组的末尾元素,也就是i=mid,j=end。然后比较arry[i]和arry[j],

  1. 如果arry[i]>arry[j],因为两段数组都是有序的,所以arry[i]>arry[mid+1...j],这些都是逆序对,我们统计出的逆序对为j-(mid+1)+1=j-mid。并且将大数arry[i]放入临时数组temp[]当中,i往前移动
  2. 如果arry[i]<arry[j],则将大数arry[j]放入temp[]中,j往前移。




#include<iostream>
#include<stdlib.h>
using namespace std;
int MergeArray(int arry[], int start, int mid, int end, int temp[])//数组的归并操作
{
//int leftLen=mid-start+1;//arry[start...mid]左半段长度
//int rightLlen=end-mid;//arry[mid+1...end]右半段长度

int i = mid;//从这开始和归并排序区别,归并为i=start,j=min+1,按数据结构方法合并两个有序子数组
int j = end;
int k = 0;//临时数组末尾坐标
int count = 0;//统计逆序对个数
//设定两个指针ij分别指向两段有序数组的头元素,将小的那一个放入到临时数组中去。
while (i >= start&&j>mid)
{
if (arry[i]>arry[j])
{
temp[k++] = arry[i--];//从临时数组的最后一个位置开始排序//temp[0-n]从大到小排列
count += j - mid;
//因为arry[mid+1...j...end]是有序的,如果arry[i]>arry[j],那么也大于arry[j]之前的元素,从a[mid+1...j]一共有j-(mid+1)+1=j-mid
}
else
{
temp[k++] = arry[j--];//没有逆序
}
}
while (i >= start)//表示前半段数组中还有元素未放入临时数组
{
temp[k++] = arry[i--];//最后一个k++可是没有元素的哦
}
while (j>mid)
{
temp[k++] = arry[j--];//最后一个k++可是没有元素的哦
}
//将临时数组中的元素写回到原数组当中去。
for (i = 0; i<=k-1; i++)
arry[end - i] = temp[i];
return count;
}
int InversePairsCore(int arry[], int start, int end, int temp[])
{
int inversions = 0;
if (start<end)
{
int mid = (start + end) / 2;
inversions += InversePairsCore(arry, start, mid, temp);//找左半段的逆序对数目
inversions += InversePairsCore(arry, mid + 1, end, temp);//找右半段的逆序对数目
inversions += MergeArray(arry, start, mid, end, temp);//在找完左右半段逆序对以后两段数组有序,然后找两段之间的逆序对。最小的逆序段只有一个元素。
}
return inversions;
}
int InversePairs(int arry[], int len)
{
int *temp = new int[len];
int count = InversePairsCore(arry, 0, len - 1, temp);
delete[] temp;
return count;
}
void main()
{
int arry[]={7,5,6,4};
//int arry[] = { 1, 3, 7, 8, 2, 4, 6, 5 };
int len = sizeof(arry) / sizeof(int);
int count = InversePairs(arry, len);
cout<<"数组中的逆序对有:"<<count<<" 对"<<endl;
cout << "归并排序后的数组为:" ;
for (int i = 0; i<len; i++)
cout << arry[i] << " ";
cout << endl;
system("pause");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值