来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof
数组中的逆序对
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:0 <= 数组长度 <= 50000
题解
方法一:归并排序
思想是「分治算法」,所有的「逆序对」来源于 3 个部分:
左边区间的逆序对;
右边区间的逆序对;
横跨两个区间的逆序对。
用这种「算贡献」的思想在合并的过程中计算逆序对的数量的时候,只在 lPtr 右移的时候计算,是基于这样的事实:当前 lPtr 指向的数字比 rPtr 小,但是比 RR 中 [0 … rPtr - 1] 的其他数字大,[0 … rPtr - 1] 的其他数字本应当排在 lPtr 对应数字的左边,但是它排在了右边,所以这里就贡献了 rPtr 个逆序对。
复杂度分析
记序列长度为 n。
- 时间复杂度:同归并排序 O ( n log n ) O(n \log n) O(nlogn)。
- 空间复杂度:同归并排序 O ( n ) O(n) O(n),因为归并排序需要用到一个临时数组。
方法二:离散化树状数组
复杂度分析
记序列长度为 n。
- 时间复杂度:离散化的过程中使用了时间代价为 O ( n log n ) O(n \log n) O(nlogn) 的排序,单次二分的时间代价为 O ( log n ) O(\log n) O(logn),一共有 n 次,总时间代价为 O ( n log n ) O(n \log n) O(nlogn);循环执行 n 次,每次进行 O ( log n ) O(\log n) O(logn)的修改和 O ( log n ) O(\log n) O(logn) 的查找,总时间代价为 O ( n log n ) O(n \log n) O(nlogn)。故渐进时间复杂度为 O ( n log n ) O(n \log n) O(nlogn)。
- 空间复杂度:树状数组需要使用长度为 n 的数组作为辅助空间,故渐进空间复杂度为 O ( n ) O(n) O(n)。
方法三:暴力求解
使用两层 for 循环枚举所有的数对,逐一判断是否构成逆序关系。
复杂度分析:
- 时间复杂度: O ( n 2 ) O(n^2) O(n2),这里 n 是数组的长度;
- 空间复杂度: O ( 1 ) O(1) O(1)。
代码
归并
class Solution {
public:
int reversePairs(vector<int>& nums) {
int n = nums.size();
//辅助数组
vector<int> tmp(n);
return mergeSort(nums, tmp, 0, n-1);
}
int mergeSort(vector<int>& nums, vector<int>& tmp, int left, int right){
if(left >= right){
return 0;
}
int mid = left + (right - left) / 2;
int count = mergeSort(nums, tmp, left, mid) + mergeSort(nums, tmp, mid+1, right);
int i = left;
int j = mid+1;
int pos = left;
while(i <= mid && j <= right){
if(nums[i] <= nums[j]){
tmp[pos++] = nums[i++];
count += (j - (mid + 1));
}
else{
tmp[pos++] = nums[j++];
}
}
for(int k = i; k <= mid; ++k){
tmp[pos++] = nums[k];
count += (j-(mid+1));
}
for(int k = j; k <= right; ++k){
tmp[pos++] = nums[k];
}
copy(tmp.begin()+left, tmp.begin()+right+1, nums.begin()+left);
return count;
}
};
树状数组
class BIT{
private:
vector<int> tree;
int n;
public:
BIT(int _n): n(_n), tree(_n + 1 ){}
//最低位,二进制数从右边起的第一个1的位置
static int lowbit(int x){
return x & (-x);
}
int query(int x){
int ret = 0;
while(x){
ret += tree[x];
x -= lowbit(x);
}
return ret;
}
void update(int x){
while(x <= n){
++tree[x];
x+= lowbit(x);
}
}
};
class Solution {
public:
int reversePairs(vector<int>& nums) {
int n = nums.size();
vector<int> tmp = nums;
//离散化
sort(tmp.begin(), tmp.end());
for(int& num:nums){
num = lower_bound(tmp.begin(), tmp.end(), num) - tmp.begin() + 1;
}
// 树状数组统计逆序对
BIT bit(n);//构造数组数组
int ans = 0;
for(int i = n-1; i >= 0; --i){
ans += bit.query(nums[i] - 1);
bit.update(nums[i]);
}
return ans;
}
};
小结
归并排序(分治思想):阶段排序结果
树状数组或二叉索引树(Binary Indexed Tree, Fenwick Tree)