其他分享
首页 > 其他分享> > 数组中的逆序对

数组中的逆序对

作者:互联网

题目链接:https://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=13&tqId=11188&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tab=answerKey.

数组中的逆序对

基本思想

拿到这个题目,最简单粗暴的方法,就是用两个for循环,一 一比较统计出逆序对,时间复杂度为O(n^2),当数组元素较多时,很有可能会超时。
那我们能想到什么方法呢?
给定一个特殊数组data={5,7,4,6}(前半段和后半段分别有序);,计算它的逆序对:
对于元素5:0个。f(0)
对于元素7:0个。f(1)
对于元素4:1个。f(2)
对于元素6:2个。f(3)
所以整个数组的逆序对cnt=0+0+1+2=3个。
我们试着对data数组进行归并排序:
data前半段数组:{5,7}
data后半段数组:{4,6}
归并过程:如图
在这里插入图片描述

  1. j 指向 后半段的4,i 指向前半段的5时,4<5。说明前半段中的 5 以及5后面的数与 4 均构成逆序对,即前半段中剩下的没来得及复制到临时数组的所有数均与 4构成逆序对,统计此时做半段没来得及复制到临时数组temp的剩余的元素数量:2。j++,判断下一个
  2. j 指向 后半段的6,i 指向前半段的5时,6>5。5 复制到临时数组。i++。左半段的 5与前面的数并不构成逆序对。(左半段升序,不构成逆序对)
  3. j 指向 后半段的6,i 指向前半段的 7 时,6<7。说明前半段中的 7 以及7后面的数与 6 均构成逆序对,即前半段中剩下的没来得及复制到临时数组的所有数均与 6构成逆序对,统计此时做半段没来得及复制到临时数组temp的剩余的元素数量:1。j++,判断下一个
  4. j 已经到末尾了,将左半段剩余的数全部复制到temp中(左半段升序,不构成逆序对)

我们发现,在归并排序时,我们可以顺便完成每个元素能构成的逆序对的统计 f(j)。由于合并是从小到大的,当右边的data[j]复制到temp中时,左边没来得及复制到temp的那些数都比data[j]大,此时只要在累加器中加上左边的剩余元素数量mid-i即可。
其实,我当时在想,这样统计出来的逆序对会不会漏或者多统计。实际上不会,在归并排序过程中,自底向上排序。每次在合并的时候,计算每个元素能构成的逆序对的数量。递归求解统计了 i 和 j 都在左边或右边的逆序对个数,“合并问题”则是统计 i 在左边,但是 j 在右边的逆序对个数。

代码

class Solution {
public:
    int InversePairs(vector<int> data) {
        cnt=0;
        vector<int> temp(data.size());
        merge_sort(data,0,data.size(),temp);
        return cnt;
    }
    //在归并排序中,计算统计逆序对的数量  左闭右开 
    void merge_sort(vector<int>& data,int start,int end,vector<int>&  temp){
    	if(end-start<=1) return;//区间中只有一个元素,一个元素认为有序,且逆序对为0
		int mid=start+(end-start)/2;//避免start+end过大溢出
		//cout<<mid;
		merge_sort(data,start,mid,temp);//归并排序左半部分 
		merge_sort(data,mid,end,temp); //归并排序右半部分
		//左右不分均排序后,合并到临时数组temp中
		int i=start,j=mid,index=start;//i指向左半边有序数组,j指向右半边有序数组,index指向temp临时数组
		
		while(i<mid||j<end){
			//右半边数组已经放完,	//或左半边数组i位置的数比右半边数组j元素要小 .这两种情况均是把左半边的数组复制到临时数组temp 
			if(j>=end||(i<mid&&data[i]<=data[j])) temp[index++]=data[i++];
			else {
				temp[index++]=data[j++];//右半边j位置元素比左半边i小,右半边复制到临时数组 
				//一旦是在将右半边数组复制到临时数组时,此时左半边剩余的还没来得及复制到temp的数均比j位置的数大,它们与j位置的数均构成逆序对
				//因此,在从右半边复制j到temp时,统计左半边还剩多少元素k,这k个数与j位置元素构成逆序对
				cnt+=mid-i; 
				cnt%=1000000007;
			}
		} 
		//将有序的temp复制回data
		i=start;
		while(i<end) {//data[i] =temp[i++];
			data[i] =temp[i];
			i++;
		}
	}
	private:
	int cnt;
};

总结

没有马上想到用归并排序来做。需要对一些经典的基础算法加深理解,其实很多题目都是在一些经典的算法中添加某些操作,来解决问题,向这个就是利用归并排序求逆序对,还有之前的利用堆排序求top k的问题等等。如果对归并排序、堆排序等经典算法有深层次的理解,就能够将它们应用到相应的实际问题中。编程路上,砥砺前行~

标签:复制到,半边,temp,数组,data,逆序
来源: https://blog.csdn.net/qq_42211773/article/details/115590473