数组中的逆序对(剑指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

首先分析题意,对于输入数组1,2,3,4,5,6,7,0, 逆序对为(1,0),(2,0)…(7,0)一共7个, 即对于数组中的每个数,其可以构成逆序对的个数为它后面且比它小的数的个数, 那么,最直观的方法显然是2层循环,当然,这样的话时间效率较低,那我们能不能找到更优的方法呢?
这里写图片描述

对于归并排序,我们应该都比较熟悉了,那么这个题目可不可以用归并排序的思路做呢? 显然是可以的。

以上图为例,我们把数组从中间分成左右2部分,然后分别计算左右2部分的逆序对的个数, 这部分递归就OK, 那我们现在的主要目的就是要计算左右2个数组之间的逆序对了, 左右2个数组计算逆序对后我们把他们弄成有序的,那计算2个有序数组之间的逆序对就只需要O(n)的时间复杂度了, 合并的时候我们需要额外的空间O(n)。

那么这样算法的时间复杂度T(n)=2T(n/2)+O(n), 故时间复杂度为O(nlogn),空间复杂度为O(n).

这样就很easy了,是不是? 不说了,直接上代码

//合并左右2个数组,nums[left:mid]和nums[mid+1:right]
int merge(vector<int>& nums, int left, int mid, int right)
{
	int sum = 0, i = mid, j = right, temp = right - left;
	vector<int> a(right - left + 1, 0);
	//2个有序数组的合并
	while (i >= left && j >= mid + 1)
	{
		if (nums[i]>nums[j])
		{
			a[temp--] = nums[i];
			sum = (sum + j - mid) % 1000000007;
			i--;
		}
		else
		{
			a[temp--] = nums[j];
			j--;
		}
	}
	if (i<left)
	{
		while (j >= mid + 1)
		{
			a[temp--] = nums[j--];
		}
	}
	if (j<mid + 1)
	{
		while (i >= left)
		{
			a[temp--] = nums[i--];
		}
	}
	for (i = left; i <= right; i++)
		nums[i] = a[i - left];
	return sum;
}

//递归计算
int mergesort(vector<int>& nums, int left, int right)
{
	if (left == right)
		return 0;
	int mid = (left + right) / 2;
	int leftcnt = mergesort(nums, left, mid) % 1000000007;
	int rightcnt = mergesort(nums, mid + 1, right) % 1000000007;
	return (leftcnt + rightcnt + merge(nums, left, mid, right)) % 1000000007;
}

int InversePairs(vector<int> data) {
	if (data.size() <= 1)
		return 0;
	return mergesort(data, 0, data.size() - 1);
}

python 的实现

# -*- coding:utf-8 -*-
class Solution:
    def swap(self, data, i, j):
        temp = data[i]
        data[i] = data[j]
        data[j] = temp
        
    ### 左边数组为 为data[left]....data[mid]
    ### 右边数组为 data[data+1]... data[right]
    def merge(self, data, left, mid, right):

        temp = [0 for i in range(right-left+1)]
        i,j = left, mid+1
        k = 0
        res = 0
        while i<mid+1 and j<right+1:
            if data[i] <= data[j]:
                temp[k] = data[i]
                i+= 1
            else:
                temp[k] = data[j]
                res += (mid+1-i)
                j+=1
            k+=1
        if i == mid+1:
            while j<right+1:
                temp[k] = data[j]
                k+=1
                j+=1
        if j == right+1:
            while i<mid+1:
                temp[k] = data[i]
                k+=1
                i+=1
        for i in range(len(temp)):
            data[left+i] = temp[i]
        return res
    
    def mergeSort(self, data, left, right):
        if left >= right:
            return 0
        mid = left + (right-left)/2
        leftCnt = self.mergeSort(data, left, mid)%1000000007
        rightCnt = self.mergeSort(data, mid+1, right)%1000000007
        return ((leftCnt + rightCnt)%1000000007 + self.merge(data, left, mid, right))%1000000007
        
    def InversePairs(self, data):
        # write code here
        if len(data) < 2:
            return 0
        return self.mergeSort(data, 0, len(data)-1)

注意python 如何采用切片的方式,像下面这种直接用data的一个子列表并不是他的引用,最后两个子数组merge 的时候,实际的数组的顺序并没有改变,会报错

def InversePairs(self, data):
        # write code here
        if len(data) < 2:
            return 0
        if len(data) == 2:
            if data[0] > data[1]:
                self.swap(data, 0, 1)
                return 1
            else:
                return 0
        mid = (len(data)-1)/2
        leftCnt = self.InversePairs(data[0:mid+1])%1000000007
        rightCnt = self.InversePairs(data[mid+1:])%1000000007
        print(data)
        return ((leftCnt + rightCnt)%1000000007 + self.merge(data, mid))%1000000007
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值