【剑指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:树状数组求解逆序数树状数组,顾名思义,就是采用数组来模拟树形结构呗,

那么衍生出一个问题?为什么不直接建树呢?答案是没有必要,

因为树状数组可以解决的问题没有必要建树,开销太大了

树状数组可以解决大部分基于区间上的更新以及区间上的求和问题

树状数组因为是模拟树的结构,其修改和查询的时间复杂度都是O(log N)

树状数组

黑色数组代表原来的数组(下面用A[i]代替),红色结构代表我们的树状数组(下面用C[i]代替),发现没有,每个位置只有一个方框,令每个位置存的就是子节点的值的和,则有

  • C[1] = A[1];
  • C[2] = A[1] + A[2];
  • C[3] = A[3];
  • C[4] = A[1] + A[2] + A[3] + A[4];
  • C[5] = A[5];
  • C[6] = A[5] + A[6];
  • C[7] = A[7];
  • C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];

比如我们要找前7项和,那么应该是SUM = C[7] + C[6] + C[4],这样一来,我们就不需要遍历A[1]到A[7]然后求和了,

只需要遍历C[7],C[6],C[4],遍历的元素数量大大减少了,性能得到了提升

其实C数组和A数组是同一个数组,只是他们不同位置上的含义不同,比如C[5]代表A[5],C[6]代表A[5]+A[6]说回逆序数,

怎么用数状数组求逆序数呢?

求逆序数的宏思路: 假设给定的序列为 4 3 2 1,我们从左往右依次将给定的序列输入,每次输入一个数temp时,

就将当前序列中大于temp的元素的个数计算出来,并累加到ans中,最后ans就是这个序列的逆序数个数。

我们可以采用数状数组标记伴随着输入,该数字是否出现过,出现过则赋值1,

统计大于当前元素值的元素个数则可以通过当前总元素个数减去小于等于当前元素值的元素个数

采用数状数组求解逆序数的时间复杂度为:O(N*log N)

class Solution
{
public:
#define max_v 9999999

    int c[max_v];

    int lowbit(int x)//找到x二进制下等于1的最低位
    {
        return x&(-x);
    }
    void update(int x,int d)//第x位置上的元素加上d
    {
        while(x<max_v)
        {
            c[x]+=d;
            x+=lowbit(x);//更新后面的数组值
        }
    }
    int getsum(int x)//求得小于等于x的和,因为元素的值为1,代表该元素出现过,也就是求得小于等于x的元素个数
    {
        int res=0;
        while(x>0)
        {
            res+=c[x];
            x-=lowbit(x);//获得前面的数组值的下标
        }
        return res;
    }
    int InversePairs(vector<int> data)
    {
        int sum=0;
        for(int i=0; i<data.size(); i++)
        {
            int x=data[i];
            x++;
            //伴随着x输入序列,统计此时序列中大于x的元素的个数
            //大于x的元素的个数=此时总元素个数-小于等于x的元素个数
            sum=(sum%1000000007+(i-getsum(x))%1000000007)%1000000007;
            update(x,1);
        }
        sum%=1000000007;
        return sum;
    }
};

解法2:归并排序求解逆序数

归并排序中先是划分到底,直到需要并的两个子数组中只有一个数字,

这两个字序列就是分别有序的,然后两个有序序列继续合并,我们所做的处理就是在两个有序序列合并的过程中进行计算即可

现在有两个子序列,下标分别是[s,m],[m+1,t]

当前面子序列中某个元素i大于后面子序列中某个元素j时,元素i到m之间的元素肯定都是大于元素j的,因为数组是有序的,

那么他们和元素j都可以组成逆序对,所以我们需要知道此时i元素到m元素之间元素的数量,其数量可以表示为m-i+1,也可以表示为j-k,

k代表放入新数组中已经有序的数字,这两种表示方法都是同一个意思

归并排序求解逆序数的时间复杂度为:O(N*log N)

归并求解逆序数

#include<bits/stdc++.h>
#include<stdio.h>
#include<memory>
#include<vector>

using namespace std;

class Solution
{
public:
#define max_v 2*100002

    int temp[max_v];
    int a[max_v];
    int  ans;

    void mer(int s,int m,int t)
    {
        int i=s;
        int j=m+1;
        int k=s;
        while(i<=m&&j<=t)
        {
            if(a[i]<=a[j])
            {
                temp[k++]=a[i++];
            }
            else
            {
                ans+=(m-i+1);
                //ans+=j-k;//求逆序数
                ans%=1000000007;
                temp[k++]=a[j++];
            }
        }
        while(i<=m)
        {
            temp[k++]=a[i++];
        }
        while(j<=t)
        {
            temp[k++]=a[j++];
        }
    }
    void cop(int s,int t)
    {
        for(int i=s; i<=t; i++)
            a[i]=temp[i];
    }
    void megsort(int s,int t)
    {
        if(s<t)
        {
            int m=(s+t)/2;
            megsort(s,m);
            megsort(m+1,t);
            mer(s,m,t);
            cop(s,t);
        }
    }
    int InversePairs(vector<int> v)
    {
        int n=v.size();
        for(int i=0; i<n; i++)
            a[i]=v[i];

        megsort(0,n-1);
        return ans%1000000007;
    }

};
原文地址:https://www.cnblogs.com/yinbiao/p/11594489.html