数组中的逆序对(分治、递归与合并)

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
输入一个数组,求出这个数组中的逆序对的总数P。
将P对1000000007取模的结果输出。 即输出P%1000000007
输入描述:
题目保证输入的数组中没有的相同的数字

数据范围:

	对于%50的数据,size<=10^4

	对于%75的数据,size<=10^5

	对于%100的数据,size<=2*10^5


示例:
输入
1,2,3,4,5,6,7,0
输出
7

思路:暴力解法是双层循环逐一判断,时间复杂度为O(n^2). 本题的更优解法是分治与合并。该题的思路过程可以参考剑指OFFER上的解释,不再赘述,下面只写出我的思路。
首先,有序数组是一种最原始的数据结构,但在很多算法上因其有序性而比无序数组快很多。数组从无序到有序的排序算法的时间复杂度普遍是O(nlogn),所以若一个无序数组的某种算法超过这个复杂度后,可以考虑先将其进行排序。
其次,分治法由两部分组成:划分成子问题递归、将子问题合并。分治法时间复杂度的关键在于合并的复杂度。事实上所有问题都能分治,但只有合并算法够简单时,分治法才有意义(关于这一点可以参考我的这篇博文)。
最后讨论本题,可以先把原数组分成两个子数组,统计两个子数组内部的逆序对,再统计两个数组之间的逆序对,两者之和即为总的逆序对数。可问题来了,我们来求两个子数组之间的逆序对,从前面的子数组中依次取出一个数(n/2个数),与后面的子数组中的每个数进行比较(n/2个数),所以合并的时间复杂度是O(n^2),根据博文可以计算出总的时间复杂度,发现没有任何优化!
根据上面三点,一个很自然的想法出现了:在统计逆序对的同时进行排序即计算逆序数的函数同时也要负起排序的任务。当两个子数组均是有序的时候,子数组之间的逆序数的计算会非常简单:用两个下标从两个数组的尾部(值较大的一端)开始向左滑动,假设这两个序号分别是 i 和 j ,分别对应两个数值 ai 和 bj (a数组在b数组之前),有:
若 ai>bj, 则 ai 大于 bj 及 bj 左边的所有数;
若 ai

对数组的排序过程用分治法,其中划分的部分和统计逆序对数是一致的,所以共同完成;而合并的部分就是将两个有序的子数组合并成大的有序数组,这需要额外的存储空间。比如数组nums[0,9] 划分成a[0,4]和b[5,9],合并a和b时需要有一个长度为10的辅助空间copy[0,9],存放排好序的元素。
当递归到上一层时,不需要把copy的值复制给nums,而是调换两者的位置(这一点非常重要)。上一层的copy就是下一层的nums, 上一层的nums就是下一层的copy。 从而我们需要合并的数组总是当前排序程度更优的那一个,而当前排序程度更差的那个数组作为辅助空间,来存储当前合并排序过后的元素。

代码:

//函数功能:把data数组的[start,end]区间排序后存储在copy数组的[start,end]区间内;
//			同时统计该区间内的逆序对数。
//			在区间[start,end]内data和copy的元素相同(虽然顺序不同)。
long long InversePairsCore(vector &data, vector ©, int start, int end){
	if (start == end){
		copy[start] = data[start];
		return 0;
	}
	int length = (end - start) / 2;
	//区间[start,end]一分为二:[start,mid]和[mid+1,end];
	//把copy数组的左部分排序并存入data左部分,统计左部分的逆序数left
	long long left = InversePairsCore(copy, data, start, start + length);
	//把copy数组的右部分排序并存入data右部分,统计右部分的逆序数right
	long long right = InversePairsCore(copy, data, start + length + 1, end);
	//把两部分有序的data合并然后存入copy,统计左部分与右部分之间的逆序数count
	int i = start + length;
	int j = end;
	int indexcopy = end;
	long long count = 0;
	while (i >= start&&j >= start + length + 1){
		if (data[i]>data[j]){
			copy[indexcopy--] = data[i--];
			count+=j - start - length;
		}
		else
			copy[indexcopy--] = data[j--];
	}
	//左右部分中有一个已经取完,则另一个依次加入copy即可
	for (; i >= start; i--)
		copy[indexcopy--] = data[i];
	for (; j >= start + length + 1; j--)
		copy[indexcopy--] = data[j];
	return left + right + count;
}

 

你可能感兴趣的:(C++,牛客网编程题,面试)