数组中的逆序对与归并中的分治思想

首先考虑归并排序:

归并排序为什么能相比普通的排序方法,将时间复杂度从O(n^2)提升至O(nlogn)?

最主要的一点是引入了两个有序数组合并的思想,真正提升效率就是在这个地方。

首先我们考虑,如果两个数组无序的话,比如:

如果使用O(n^2)的方法,在这样一个数组中,每一个数都要跟其他的数比较一下,这样复杂度就为n*n=n^2

而如果这两个数组是有序的呢?

我们用两个指针p1和p2分别指向两个数组的尾部:
首先49比97小,97放入排序数组的尾部,因为它是最大的;

然后移动p2至65,再次与49做对比,将65放入倒数第二个位置,因为这是次大的。

然后我们就不需要比较了,因为38比49一定小,因此对38来说他不用比较一次。

这样我们看两个有序数组的排序实际上复杂度只需要O(m+n)

对于同样一个数38,我们在O(n^2)的方法中仍然要与49比,与65比,与97比,比完了之后才发现,哦原来我是最小的。

但是在两个有序数组的排序中,38是完全不用比较一次的。

因为数组的有序提供了额外信息,比如49比65和97都小,连比38大的数都比65和97小,那38我们还用比较吗?

这就是有序数组提供额外信息对我们排序效率提升的地方。

而这种额外信息是有代价的,这个代价就是logn的代价,也就是说将整个数组不断分治,分为logn层,每一层利用上一层的额外信息进行排序,排序后作为下一层的额外信息。

因此实际上归并排序nlogn的复杂度的来源就是:将无序数组排序问题转变成两个有序数组的排序问题,这样使复杂度从n^2提升至n,但有序又需要付出logn的代价,因此实际上对某一层来说,复杂度为O(n),共有logn层,来时间复杂度为O(nlogn).

有一点值得注意的是,如果在两个有序数组的合并过程中,不采用这种双指针的排序方式,还采取普通的O(n^2)的排序方式,复杂度不会有任何改善。

以下图中38为例:

38在第一次排序中跟49比,在第二次排序中跟65和97所在的数组比,在第三次跟76,13,27所在的数组比,实际上对某个数来说,在归并排序的过程中他还是在跟数组中所有的数在比较,只是在某些过程中的某些比较被避免了,从而提升了效率,比如在49和65与97比完后,38本来是要跟65和97比的,但由于38和49是有序的,我们知道了这种比较是没必要的,因此将这种比较省略了。

本质上,归并还是相当于某个数跟数组中所有的数都比较后找到自己应该属于的位置,但由于分治归并的思想,规避了这些比较中没必要的比较,从而提升了效率。

就像普通排序中38要跟所有的数比,而归并中,它的某些比较被省去了而已,但从流程上来说,每次数组合并,像[38 49]与[65 97]合并,[38 49 65 97]与[13 27 76]合并,38都还是要与另一个数组中的数做比较的,只是因为有序数组合并的算法,规避了某些没必要进行的排序。

下面进入正题,对数组中的逆序这道题的分析:

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。

输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

首先我们最容易想到的思路就是暴力搜索,对某个位置i的元素,将i+1~end的所有元素斗鱼i位置的元素相比。

但这种方式的复杂度太高了,O(n^2)的复杂度,在这个题里面一定是会超时的。

然后我们在仔细思考这个题:

以 5 6 2 1这个序列来说:

我们对5使用这个搜索过程,得到对于5,它的逆序对个数是2。

但如果[5 6]和[2 1]都是有序的,也就是[5 6] [1 2]时,5与[1]的对比是没有必要的,因为5已经比这个数组中最大的2都要大了,那肯定能和2以前的所有元素组成逆序对。

这个思路是不是很熟悉?

在上面进行归并排序时,我们对49 38 65 97排序时就是如此,对于有序的[38 49]和[65 97],我们不用对38在做比较了。

因此我们可以利用这个思路:

对[5 6] [2 1]先转变为有序,在进行逆序对计算。

前面提过:

本质上,归并还是相当于某个数跟数组中所有的数都比较后找到自己应该属于的位置,但由于分治归并的思想,规避了这些比较中没必要的比较,从而提升了效率。

在合并的过程中,某个数起始本质上还是在不断的在与在它后面位置的元素作比较,只是因为有序的存在,从而导致规避了没有必要的比较,如5在合并的过程中还是在与2和1比较,只是和1的比较被规避了。

不要认为归并的过程中5只与[2,1]的数组进行了比较,事实上,在合并为[1 2 5 6]后,下一次合并中5还是会与另外一半的四个[x y z d]比较,只是比较规程中有些没必要的被规避了而已。

代码中值得注意的只有:

ans=ans+(p2-(mid+1)+1);这代表如果p1的元素比p2大,则p1与p2前的所有元素都构成了逆序对。
class Solution {
public:
	typedef long long ll;
    int InversePairs(vector<int> data) {
	  return InversePairsCore(data,0,data.size()-1)%1000000007;
    }
	ll InversePairsCore(vector<int>& data,ll start,ll end)
	{
		if(start>=end)
			return 0;
		ll mid=(start+end)/2;
		ll nl=InversePairsCore(data,start,mid);
		ll rl=InversePairsCore(data,mid+1,end);
		vector<int> tmp(end-start+1);
		ll p1=mid,p2=end;
		ll p3=tmp.size()-1;
		ll ans=nl+rl;
		while(p1>=start&&p2>=mid+1)
		{
			if(data[p1]>data[p2])
			{
				ans=ans+(p2-(mid+1)+1);
				tmp[p3]=data[p1];
				p1--;
				p3--;
			}
			else{
				tmp[p3]=data[p2];
				p2--;
				p3--;
			}
		}
		if(p1>=start)
		{
			while(p3>=0)
			{
				tmp[p3]=data[p1];
				p1--;
				p3--;
			}
		}
		if(p2>=mid+1)
		{
			while(p3>=0)
			{
				tmp[p3]=data[p2];
				p2--;
				p3--;
			}
		}
		for(ll i=0;i<tmp.size();i++)
			data[i+start]=tmp[i];
		return ans;
	}
};

  

原文地址:https://www.cnblogs.com/lxy-xf/p/11392674.html

时间: 2024-10-13 22:44:24

数组中的逆序对与归并中的分治思想的相关文章

[剑指offer]51-数组中的逆序对(归并排序)

题目链接 https://www.nowcoder.com/questionTerminal/96bd6684e04a44eb80e6a68efc0ec6c5 题意 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数P.并将P对1000000007取模的结果输出. 即输出P%1000000007.题目保证输入的数组中没有的相同的数字. 解题思路 在归并排序的过程中计逆序对.时间复杂度O(nlogn),空间复杂度O(n). 将

【BUAA 1246】数组中的逆序对

数组中的逆序对 归并算法的灵活运用 之前就听学长说这个用来算逆序对很好用 一直没有自己实现过 这次虽然比赛中没去做 赛后自己实现了出来 很有成就感! 也对归并算法透彻领悟了 真的好多算法都需要理解 而不是死记硬背 之前就是硬背过的 过一段时间就忘光了 这次彻底明白了算法执行过程 比起做出这道题 更宝贵 代码如下 #include <bits/stdc++.h> #define LL long long using namespace std; int num[111111]; int n[11

数组中的逆序对(Java实现)

来源:剑指offer 逆序对定义:a[i]>a[j],其中i<j 思路:利用归并排序的思想,先求前面一半数组的逆序数,再求后面一半数组的逆序数,然后求前面一半数组比后面一半数组中大的数的个数(也就是逆序数),这三个过程加起来就是整体的逆序数目了. 易错点:第二个方法在归并时,需要array的左右子数组是已排好序的数组,归并的结果是得到排好序的数组copy.因此在递归调用iPairs时,方法的前2个参数是颠倒的,这样得到的array才是排好序的. 比如第一次时用copy当辅助数组对array排序

剑指offer_数组中的逆序对

题目描述 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数P. 并将P对1000000007取模的结果输出. 即输出P%1000000007 本题采用归并排序,归并排序算法我在前一篇博客里写到过,在那个基础上进行修改即可!(强烈建议先理解归并排序的具体算法后,再来做此题) public class Solution36 { private int count = 0; //记录次数 private int[] copy ;

(剑指Offer)面试题36:数组中的逆序对

题目: 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. 思路: 1.顺序扫描 顺序扫描整个数组,每扫描到一个数字,就将该数字跟后面的数字比较,如果大于的话,则这两个数字构成了逆序对.(比较简单,这里就不贴出代码) 时间复杂度:O(n^2) 2.归并思想 将数组不断地等分为两个子数组,然后自下而上地统计两个子数组各自的逆序对以及合并后的逆序对. 假设两个子数组左右分别为A,B,长度分为lenA,lenB,子数组已排好序,

36 - 求数组中的逆序对

题目描述:http://ac.jobdu.com/problem.php?pid=1348 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. 如输入{7,5,6,4}中,共存在5个逆序对:(7,6) (7,5) (7,4) (6,4) (5,4) 解析: 直观思路是: 从第一个数字开始,让它和后面的每个数字比较,找出所有逆序对.时间复杂度O(n2) 改进思路: 只要让一个数字和其他的每个数字比较,那么时间就是O(n2).

面试题36_数组中的逆序对

题目描述 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. 解题思路 思路一:暴力搜索,一次判断每一个数,其之后的数有没有比它小的,若有则计数加一.时间O(n^2) 思路二:利用归并排序方法,在归并的时候,计算逆序对数目.具体过程看代码. 时间O(nlogn) 实现代码 class Solution { public: int InversePairs(vector<int> data) { if(data.empty

剑指offer:数组中的逆序对

题目描述在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数P.并将P对1000000007取模的结果输出. 即输出P%1000000007输入描述:题目保证输入的数组中没有的相同的数字 数据范围: 对于%50的数据,size<=10^4 对于%75的数据,size<=10^5 对于%100的数据,size<=2*10^5 示例1输入 1,2,3,4,5,6,7,0输出 7 # -*- coding: utf-8 -*

剑指offer35:数组中的逆序对

1 题目描述 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数P.并将P对1000000007取模的结果输出. 即输出P%1000000007 2 思路和方法 利用归并排序的思想,先把数组分隔成子数组,先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目.注意在合并两个已排序的子数组后,要更新数组.O(n*log(n)). 3 C++核心代码 1 class Solution { 2 public