数组中的逆序对

题目链接: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的问题等等。如果对归并排序、堆排序等经典算法有深层次的理解,就能够将它们应用到相应的实际问题中。编程路上,砥砺前行~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值