1. 题目来源
链接:lc493. 翻转对
2. 题目说明
3. 题目解析
方法一:归并排序+逆序对
和逆序对一个意思,树状数组不会,只能拿归并排序做,毕竟归并排序能用到的地方屈指可数。
在此需要注意,不要在二路归并排序的时候来记数,这是和逆序对有所区别。
疑问:
2 3 4
1 5
二路归并时, i = 0,指向 2, j = 0, 指向 1。然而, nums[i] > nums[j] 走 else 部分,
造成 j++, j 指向 5。本来还能和 (1,3),(1,4)构成逆序对的 1 就被跳过了。故遗漏了很多情况。
但是, 二路归并的代码又无法进行改动。我想了很长时间也没想出来整合到一起的方法...
可以针对这个 j,通过 while 枚举后面所有的 i 找到第一个满足 nums[i] > nums[j] * 2 的 i 位置,就能知道 [i,mid] 中所有数字都可以和这个 j 构成逆序对关系。可以将整个代码整合到二路归并中。但是想完稍微实现了下还是问题很大。。。
有个错误样例 [-5, -5] 这个,
这样统计也是很不错的,关键是 j 别胡乱跳到下一个就行了
for (int i = l, j = mid + 1; i <= mid; ++i) {
while (j <= r && nums[j] * 2ll < nums[i]) res += mid - i + 1, ++j;
}
然而在二路归并中,j 的下标是无法进行太多控制的。
我人晕掉了,等我回来码树状数组的时候,再解决该问题。
代码:
class Solution {
public:
vector<int> tmp = vector<int>(50005, 0); // 全局数组初始化方式
int merge_sort(vector<int> &nums, int l, int r) {
if (l >= r) return 0;
int mid = l + r >> 1;
int res = merge_sort(nums, l, mid) + merge_sort(nums, mid + 1, r);
for (int i = l, j = mid + 1; i <= mid; ++i) {
while (j <= r && nums[j] * 2ll < nums[i]) ++j; // 转long long 防溢出
res += j - (mid + 1);
}
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (nums[i] <= nums[j]) tmp[k ++] = nums[i ++];
else tmp[k ++] = nums[j ++];
while (i <= mid) tmp[k ++] = nums[i ++];
while (j <= r) tmp[k ++] = nums[j ++];
for (int i = l, j = 0; i <= r; ++i, ++j) nums[i] = tmp[j];
return res;
}
int reversePairs(vector<int>& nums) {
return merge_sort(nums, 0, nums.size() - 1);
}
};