• 剑指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都已排好序,然后再对它们进行合并,并在合并的过程中计算逆序对

    1580017549334

    下标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)等级的

  • 相关阅读:
    VBoxManage命令详解
    十条nmap常用的扫描命令
    2015-12-16 第八天笔记整理-第二部分
    2015-12-13 第八天笔记整理-第一部分
    2015-12-06 第七天课程笔记
    2015-12-04 学习笔记整理
    2015-11-22 第五天
    选择控制语句和循环结构
    数据类型和运算符
    常用DOS指令
  • 原文地址:https://www.cnblogs.com/virgildevil/p/12234140.html
Copyright © 2020-2023  润新知