每日一题(45) - 数组中的逆序对

题目来自剑指Offer

题目:


举例:


思路:

方法(1):

暴力方法:对于数组中任意两个数,均做一次判定,判断是否为逆序对。

时间复杂度:O(n^2)

方法(2):类似于归并排序。

(1)首先把数组分成两个长度均等的数组

(2)分布对这两个数组排序(小到大)。

(3)之后再求逆序对,同时对把这两个数组合并到一个数组

时间复杂度为O(nlogn)

为什么这么可以减少时间消耗呢?

计算逆序对时,先算每一个区间内的逆序对,之后再算区间间的逆序对。

在算区间间的逆序对时,两个数组均是有序的,对于有序数组中的每一个元素,可以再O(1)的时间算出其逆序对

举例:

对于数组:7,5,4,6

先分成两个数组 7,5和4,6.

之后对两个数组进行排序,得到:

有序数组(1):5,7

有序数组(2):4,6

之后逆序归并,并求逆序

最大数为7,由于7在左边数组中,且7>6(右边数组的最大数),则7与第二个数组6左边的数据均产生逆序对。同时把7放到临时数组中。(本次循环找出两个逆序对)

之后取得最大值为6,由于6在右边的数组中,则不会产生逆序对。则直接放入临时数组中。

之后取得最大值为5、由于5在左边数组中,且5>4(右边数组的最大数),则5与第二个数组4左边的数据均产生逆序对。同时把5放到临时数组中。(本次循环找出一个逆序对)

。。。

代码:

#include <iostream>
#include <assert.h>
using namespace std;
/*对数组nArr的有序区间[nStart,nMid]和[nMid+1,nEnd]进行归并,放到nArr中*/
void Merge(int nArr[],int nStart,int nMid,int nEnd,int* pTmp,int& nCount)
{
	int nLeftStart = nStart;
	int nLeftEnd = nMid;
	int nRightStart = nMid + 1;
	int nRightEnd = nEnd;
	assert(nEnd - nStart + 1 > 0);
	int nCur = nEnd;
	while (nLeftStart <= nLeftEnd && nRightStart <= nRightEnd)
	{
		if (nArr[nLeftEnd] > nArr[nRightEnd])
		{
			nCount += nRightEnd - nRightStart + 1;
			pTmp[nCur--] = nArr[nLeftEnd];
			
			nLeftEnd--;
		}
		else
		{
			pTmp[nCur--] = nArr[nRightEnd];
			nRightEnd--;
		}
	}
	assert(nCur >= nStart && nCur <= nEnd);
	//处理剩余的数组,注意,剩余的肯定是那些比较小的数,此时无论这些小数在左边还是在右边都不对逆序对个数产生影响
	while (nLeftStart <= nLeftEnd)
	{
		//剩余的数在左边数组中
		pTmp[nCur--] = nArr[nLeftEnd];
		nLeftEnd--;
	}
	while (nRightStart <= nRightEnd)
	{
		//剩余的数在右边数组中
		pTmp[nCur--] = nArr[nRightEnd];
		nRightEnd--;
	}
	assert(nCur == nStart - 1);
	//把临时数组中的数据移动到新数组中
	for (nCur = nEnd;nCur >= nStart;nCur--)//和下面的函数区别之处
	{
		nArr[nCur] = pTmp[nCur];
	}
}
/*对nArr的区间[nStart,nEnd]进行排序,并把排序的结果放入nArr中*/
void FindReverseOrder(int nArr[],int nStart,int nEnd,int* pTmp,int& nCount)
{
	if (nStart < nEnd)
	{
		int nMid = (nStart + nEnd) >> 1;
		FindReverseOrder(nArr,nStart,nMid,pTmp,nCount);/*对nArr的区间[nStart,nMid]排序,并把结果放入nArr中*/
		FindReverseOrder(nArr,nMid + 1,nEnd,pTmp,nCount);/*对nArr的区间[nMid+1,nEnd]排序,并把结果放入nArr中*/
		Merge(nArr,nStart,nMid,nEnd,pTmp,nCount);/*对nArr的两个区间[nStart,nMid]和[nMid+1,nEnd]排序,并把结果放入nArr中*/
	}
}

int FindReverseOrder(int nArr[],int nLen)
{
	assert(nArr && nLen > 0);
	int nCount = 0;
	int* pTmp = new int[nLen];
	FindReverseOrder(nArr,0,nLen - 1,pTmp,nCount);
	return nCount;
}

int main()
{
	//int nArr[100] = {7,5,6,4};
	int nArr[100] = {7,5,6,4,1,9,4};
	cout<<FindReverseOrder(nArr,7)<<endl;
	system("pause");
	return 1;
}

上述代码存在一个问题:

对于两个数组进行归并时,总是把归并的结果先放入pTmp中,之后在把排序好的数组转存到数组nArr中。

注意,这里就存在一个冗余操作。即数据的来回存放。不过该优化不会改变时间复杂度。

优化代码:

思想:当结果归并到临时数组pTmp后,就先不用归并到nArr中,可以直接在数组pTmp处理,在之后那次的处理中,临时数组就为nArr了。即临时数组不仅仅限定于pTmp,这样就不用对每次归并的结果转来转去的啦。

#include <iostream>
#include <assert.h>
using namespace std;
/*求解数组nArr中的两个有序区间的逆序对,并把两个有序区间归并到pTmp中*/
void Merge(int nArr[],int nStart,int nMid,int nEnd,int* pTmp,int& nCount)
{
	int nLeftStart = nStart;
	int nLeftEnd = nMid;
	int nRightStart = nMid + 1;
	int nRightEnd = nEnd;
	assert(nEnd - nStart + 1 > 0);
	int nCur = nEnd;
	while (nLeftStart <= nLeftEnd && nRightStart <= nRightEnd)
	{
		if (nArr[nLeftEnd] > nArr[nRightEnd])
		{
			nCount += nRightEnd - nRightStart + 1;
			pTmp[nCur--] = nArr[nLeftEnd];

			nLeftEnd--;
		}
		else
		{
			pTmp[nCur--] = nArr[nRightEnd];
			nRightEnd--;
		}
	}
	assert(nCur >= nStart && nCur <= nEnd);
	//处理剩余的数组,注意,剩余的肯定是那些比较小的数,此时无论这些小数在左边还是在右边都不对逆序对个数产生影响
	while (nLeftStart <= nLeftEnd)
	{
		//剩余的数在左边数组中
		pTmp[nCur--] = nArr[nLeftEnd];
		nLeftEnd--;
	}
	while (nRightStart <= nRightEnd)
	{
		//剩余的数在右边数组中
		pTmp[nCur--] = nArr[nRightEnd];
		nRightEnd--;
	}
	assert(nCur == nStart - 1);
}
/*对nArr的区间[nStart,nEnd]进行排序,并把排序的结果放入pTmp中*/
void FindReverseOrder(int nArr[],int nStart,int nEnd,int* pTmp,int& nCount)
{
	if (nStart < nEnd)
	{
		int nMid = (nStart + nEnd) >> 1;
		FindReverseOrder(pTmp,nStart,nMid,nArr,nCount);//对数组pTmp的区间[nStart,nMid]进行排序,之后放到数组Arr中
		FindReverseOrder(pTmp,nMid + 1,nEnd,nArr,nCount);//对数组pTmp的区间[nMid + 1,nEnd]进行排序,之后放到数组Arr中
		Merge(nArr,nStart,nMid,nEnd,pTmp,nCount);//对数组nArr进行排序,把结果放到数组pTmp中
		//在调用本次递归时,向nArr传参的是pTmp,而向pTmp传参的是nArr,即相对于上一层递归来说,实际上有序数组是在数组nArr中
	}
}

int FindReverseOrder(int nArr[],int nLen)
{
	assert(nArr && nLen > 0);
	int nCount = 0;
	int* pTmp = new int[nLen];
	memcpy(pTmp,nArr,nLen * sizeof(int));//很重要,别忘了。
	FindReverseOrder(pTmp,0,nLen - 1,nArr,nCount);
	//此时数组nArr中存放的元素是有序的

	//for (int i = 0;i < 7;i++)
	//{
	//	cout<<nArr[i]<<" ";
	//}

	return nCount;
}

int main()
{
	//int nArr[100] = {7,5,6,4};
	//int nArr[100] = {7,5,6,4,1,9,4};
	int nArr[100] = {7,5,6,4,1,9,4};
	cout<<FindReverseOrder(nArr,7)<<endl;
	system("pause");
	return 1;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值