算法题学习---数组中的逆序对

132 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第21天,点击查看活动详情

题目

描述

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

数据范围: 对于 50% 的数据, size≤10^4 对于 100% 的数据,size≤10^5

数组中所有数字的值满足 0≤val≤10^9

要求:空间复杂度O(n),时间复杂度 O(nlogn)

输入描述:

题目保证输入的数组中没有的相同的数字

示例1

输入:
[1,2,3,4,5,6,7,0]
返回值:
7

示例2

输入:
[1,2,3]
返回值:
0

分析

暴力遍历法

这是最容易想到的方法,两重遍历,检查每一个元素后面的元素是否小于该元素,如果是,则计数加一,完整统计之后,再做取模运算,如下:

class Solution {
public:
    int InversePairs(vector<int> data) {
        long long sum = 0;
        for(int i = 0; i < data.size(); ++i)
        {
            for(int j = i + 1; j < data.size(); ++j)
            {
                if(data[i] > data[j])
                {
                    sum++;
                }
            }
        }
        return sum % 1000000007;
    }
};

方法很简单,但是缺陷也很明显,时间复杂度为O(n^2)

分治法

我们重新来思考一下逆序对的含义:在一个元素的右边出现的比它小的数,两者组成逆序对。那么什么情况下是没有逆序对的呢?答案是已经排序好的数组,而且是从小到大排序,反之,从大到小排序的数组是逆序对最多的情况,为(n-1)+(n-2)+...+2+1。那么我们是否可以思考一下对这个无序数组进行排序,在排序的过程中记录下逆序对的信息。

从上面的分析中,我们已经有发现一些端倪,这里我们要使用的是归并排序,因为只有使用归并排序,在merge阶段,我们才有机会统计相关的逆序对信息。假设我们现在已经有两个已经排序的数组:left_part,right_part,假设它们已经从大到小(便于我们进行统计)排序,此时我们需要需要对两个数组进行归并,使用迭代器leftIter和rightIter从左到右分别遍历:

  • 如果*leftIter > rightIter,说明该元素大于右边剩下的所有元素,我们需要累加right_part.end() - rightIter,还需要将leftIter写入归并的位置,并递增到下一个位置;
  • 如果*leftIter < rightIter,说明该元素小于当前的元素,将rightIter写入归并的位置,并递增到下一个位置;
  • 当遍历完成leftIter或rightIter没有到各自数组的末尾时,我们只需要一一写入即可,不涉及逆序对统计。

最后将当前逆序对更新为取模之后的结果。

代码示例

如上面所说:

需要注意以下几点:

  • 分治的终点是left和right相等,即只有一个元素时;
  • merge过程中需要考虑到排序方式,下面采用的是从大到小,如果是从小到大排序,也是可以的,只是遍历方向要换一下;
  • 最后还需要将各自没有比较完的元素一一放回归并结果中。
class Solution {
public:
    int InversePairs(vector<int> data) {
        int count = 0;
        int left = 0;
        int right = data.size() - 1;
        MergeSort(data, left, right, count);
        return count;
    }

    void MergeSort(vector<int> &data, int left, int right, int &count)
    {
        // stop split
        if(left == right)
        {
            return;
        }
        // split
        int mid = (left + right) / 2;
        MergeSort(data, left, mid, count);
        MergeSort(data, mid + 1, right, count);
        // merge
        vector<int> left_part(data.begin() + left,data.begin() + mid + 1);
        vector<int> right_part(data.begin() + mid + 1,data.begin() + right + 1);
        auto leftIter = left_part.begin();
        auto rightIter = right_part.begin();
        int index = left;
        while(leftIter != left_part.end() && rightIter != right_part.end())
        {
            if(*leftIter > *rightIter)
            {
                data[index++] = *leftIter++;
                count += right_part.end() - rightIter;
            }
            else if(*leftIter < *rightIter)
            {
                data[index++] = *rightIter++;
            }
        }
        while(leftIter != left_part.end())
        {
            data[index++] = *leftIter++;
        }
        while(rightIter != right_part.end())
        {
            data[index++] = *rightIter++;
        }

        count %= 1000000007;
    }
};