剑指OFFER 数组中的逆序对

Posted virgildevil

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了剑指OFFER 数组中的逆序对相关的知识,希望对你有一定的参考价值。

剑指OFFER 数组中的逆序对

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。
即输出P%1000000007

解题思考记录

初看这道题,马上想到了暴力解法,就是一个一个去比较(类似冒泡排序),复杂度O(n^2)

然后第二次思考,想到了动态规划,每次找逆序对的时候利用上次的结果,后面贴有代码,可以感觉到比暴力解法稍微好了一点,但复杂度仍然是O(n^2)的,卡在了75%通不过了,测试数据比较大

然后实在没办法了,上网看题解,决定使用归并的方法来解决,

归并解法

在写归并的接法之前,我先把归并排序手写了一遍,很久没有写归并排序了,先热一下身,回顾一下

归并排序

#include <iostream>
#include <vector>
using namespace std;

vector<int> data;

void merge(int left1,int right1,int left2,int right2)
{
    vector<int> tmp;

    int cur1=left1,cur2=left2;

    //类似这种比较头结点的方式,都先同步后异步,易于理解和编写
    while(cur1<=right1 && cur2<=right2)
    {
        if(data[cur1]<data[cur2])
        {
            tmp.push_back(data[cur1++]);
        }else{
            tmp.push_back(data[cur2++]);
        }
    }
    while(cur1<=right1){
        tmp.push_back(data[cur1++]);
    }
    while(cur2<=right2){
        tmp.push_back(data[cur2++]);
    }

    int size = tmp.size();
    for(int i=0;i<size;i++){
        data[left1+i] = tmp[i];
    }
}
void recur(int left,int right)
{
    if(left>=right)return;

    int mid = (left+right)/2;
    recur(left,mid);
    recur(mid+1,right);

    merge(left,mid,mid+1,right);
}

int main() {
    data = vector<int> {3,4,5,2,1,8,9,10,0,-1,-2};
    int size = data.size();

    recur(0,size-1);
    for(int i=0;i<size;i++){
        cout<<data[i]<<" ";
    }
    cout<<endl;

    return 0;
}

题解

根据归并排序的代码稍加修改,就可以应用到本题中

class Solution {
public:
    int count = 0;
    void merge(vector<int>& data,int left1,int right1,int left2,int right2)
    {
        vector<int> tmp;

        int cur1=left1,cur2=left2;

        //类似这种比较头结点的方式,都先同步后异步,易于理解和编写
        while(cur1<=right1 && cur2<=right2)
        {
            if(data[cur1]<=data[cur2])
            {
                tmp.push_back(data[cur1++]);
            }else{
                count += right1-cur1+1;//核心是要理解这一句
                count %= 1000000007;
                tmp.push_back(data[cur2++]);
            }
        }
        while(cur1<=right1){
            tmp.push_back(data[cur1++]);
        }
        while(cur2<=right2){
            tmp.push_back(data[cur2++]);
        }

        int size = tmp.size();
        for(int i=0;i<size;i++){
            data[left1+i] = tmp[i];
        }
    }
    void recur(vector<int> &data,int left,int right)
    {
        if(left>=right)return;

        int mid = (left+right)/2;
        recur(data,left,mid);
        recur(data,mid+1,right);

        merge(data,left,mid,mid+1,right);
    }
    int InversePairs(vector<int> data) {
        if(data.size()==0)return 0;
        recur(data,0,data.size()-1);
        return count;
    }
};

代码分析

那行重点强调的代码如何理解呢?

归并排序的时候一定是如下图所示,数组1和数组2都已排好序,然后再对它们进行合并,并在合并的过程中计算逆序对

技术图片

下标0时,逆序对3

下标1时,逆序对0

下标2时,逆序对0

下标3时,逆序对1

把这些加起来就能得到一次合并的逆序对数,再把所有的合并过程总全部累积,那么就得到所有的逆序对数

动态规划解法(时间复杂度达不到要求)

class Solution {
public:

    int InversePairs(vector<int> data) {
        int size = data.size();
        if(size == 0)return 0;
        vector<int> dp;
        dp.resize(size+1);
        
        dp[0] = 0;//无意义
        dp[1] = 0;
        for(int i=2;i<=size;i++){
            int per = 0;
            for(int j=0;j<i-1;j++)
            {
                if(data[j]>data[i-1])per++;
            }

            dp[i] = dp[i-1] + per;
            dp[i] = dp[i]%1000000007;
        }
        return dp[size];
    }
};

只通过了75%的用例,复杂度计算
[ 1+2+3+...+n=frac{n(n+1)}{2} ]
仍然是O(n^2)等级的

以上是关于剑指OFFER 数组中的逆序对的主要内容,如果未能解决你的问题,请参考以下文章

剑指offer——数组中的逆序对

剑指OFFER 数组中的逆序对

剑指 Offer 51. 数组中的逆序对

剑指offer-36:数组中的逆序对

剑指Offer——数组中的逆序对

剑指offer35:数组中的逆序对