描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007
数据范围: 对于 50%50\%50% 的数据, size≤104size\leq 10^4size≤104
对于 100%100\%100% 的数据, size≤105size\leq 10^5size≤105
数组中所有数字的值满足 0≤val≤10000000 \le val \le 10000000≤val≤1000000
要求:空间复杂度 O(n)O(n)O(n),时间复杂度 O(nlogn)O(nlogn)O(nlogn)
输入描述:
题目保证输入的数组中没有的相同的数字
示例1
输入:
[1,2,3,4,5,6,7,0]
返回值:
7
示例2
输入:
[1,2,3]
返回值:
0
题解:
对于该题如果使用暴力算法,使用for循环判断是否有比当前值大的值进行统计。我们首先看下暴力算法
class Solution {
private:
const int kmod = 1000000007;
public:
int InversePairs(vector<int> data) {
int ret = 0;
int n = data.size();
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (data[i] > data[j]) {
ret += 1;
ret %= kmod;
}
}
}
return ret;
}
};
这种算法会使时间复杂度上升为,那有没有一种比较高效的算法呢。对于逆序对我们采用归并思想来解题。
归并排序过程主要有以下两个操作:
- 递归划分整个区间为基本相等的左右两个区间
- 合并两个有序区间
递归划分的终止标准是只有一个元素时开始向上合并,然后在程序中进行左右划分操作
class Solution {
public:
int mod = 1000000007;
int mergeSort(int left, int right, vector<int>& data, vector<int>& temp)
{
if(left >= right) //递归终止条件
return 0;
int mid = left + (right - left)/2; //求数组下标中值
int res = mergeSort(left,mid,data,temp) +mergeSort(mid+1,right,data,temp);//统计递归下去得到的逆序对数和
res %= mod; //防止内存溢出
int i=left,j=mid+1;
for(int k=left; k<=right; k++){
temp[k] = data[k]; //将待排序数组复制到新空间
}
for(int k=left; k<=right; k++){ //对数组进行排序操作,并统计逆序对数
if(i == mid+1) //当左侧数组排完时,i=mid+1
data[k] = temp[j++]; //将右侧数组加到排序数组中
else if(temp[i]<=temp[j] || j==right+1) //左侧数组小于右侧数组 或 右侧数组排完
data[k] = temp[i++]; //将左侧数组加到排序数组中
else{
data[k] = temp[j++]; //左侧数组大于右侧时,将右侧加到排序数组中
res += mid-i+1; //统计逆序对数
}
}
return res % mod;
}
int InversePairs(vector<int> data) {
int n = data.size();
vector<int> res(n);
return mergeSort(0,n-1,data,res);
}
};
在这里有一个不好理解的点是统计逆序对数时用res += mid - i +1
当左侧比较元素大于右侧比较元素时,左侧i位置到mid位置的所有元素均大于右侧比较元素(因为是归并排序上来的,左右两侧的数组都是有序的),此时增加的逆序对数为mid - i + 1
然后再逐级向上传递统计的逆序对数再进行进行统计,就得到了总的逆序对数。