开启掘金成长之旅!这是我参与「掘金日新计划 · 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;
}
};