1. 题目解析
题目链接:LCR 170. 交易逆序对的总数
这个问题的理解其实相当简单,只需看一下示例,基本就能明白其含义了。
2.算法原理
归并排序的基本思路
归并排序将数组从中间分成两部分,在排序的过程中,逆序对的来源分为以下三类:
- 左子数组内部的逆序对
- 右子数组内部的逆序对
- 跨越左右子数组的逆序对
最终的逆序对总数是这三类逆序对的总和。归并排序的整体步骤如下:
- 排序左子数组
- 排序右子数组
- 合并两个有序子数组
利用归并排序统计逆序数的原理
在归并排序的合并过程中,左、右子数组始终保持有序状态。我们可以利用这一特性快速统计跨越左右子数组的逆序对数量,而不必遍历所有可能的组合。
具体计算逆序数的方法
合并两个有序数组时,可以通过以下两种方式之一统计逆序数:
- 统计某个数之前的有多少个数比它大
- 统计某个数之后的有多少个数比它小
我们重点分析第一种方式的原理。
示例分析
假设两个有序数组和辅助数组为 left = [5, 7, 9]
,right = [4, 5, 8]
和 help = []
。通过合并的过程可以求得逆序数。定义如下变量:
cur1
:遍历left
数组的指针cur2
:遍历right
数组的指针ret
:记录逆序数的计数器
合并的具体步骤如下:
-
第一轮:
left[cur1] > right[cur2]
。因为left
数组中[cur1, 2]
区间的所有元素均大于right[cur2]
,这些元素可以与right[cur2]
构成逆序对。因此,更新ret += 3
并将right[cur2]
放入help
数组,同时cur2++
。 -
第二轮:
left[cur1] == right[cur2]
。此时right[cur2]
可能与left
中的其他元素形成逆序对,因此将left[cur1]
放入help
数组。没有新增逆序对,不更新ret
。 -
第三轮:
left[cur1] > right[cur2]
。与第一轮类似,left[cur1, 2]
区间内的元素均大于right[cur2]
,更新ret += 2
,并将right[cur2]
放入help
数组,cur2++
。 -
第四轮:
left[cur1] < right[cur2]
。left[cur1]
比right
中的所有元素小,不构成逆序对。直接将left[cur1]
放入help
数组,不更新ret
。 -
第五轮:
left[cur1] > right[cur2]
。此时left
中的元素能与right[cur2]
构成逆序对,更新ret += 1
,并将right[cur2]
放入help
数组。
处理剩余元素
在合并过程中,如果 left
中还有剩余元素,说明这些元素已经与 right
中的元素计算过,不会新增逆序对。直接将剩余元素放入 help
数组。如果 right
中还有剩余元素,则这些元素均比 left
中的元素大,同样不会构成逆序对。
小结
通过上述方式利用归并排序的合并过程,可以快速统计逆序数。复杂度为 O(N log N)
,相较于暴力解法的 O(N^2)
效率更高。
3.代码编写
class Solution {
int tmp[50010];
public:
int reversePairs(vector<int>& nums) {
return mergeSort(nums, 0, nums.size() - 1);
}
int mergeSort(vector<int>& nums, int left, int right) {
if (left >= right)
return 0;
int ret = 0;
// 1. 找中间点,将数组分成两部分
int mid = (left + right) >> 1;
// [left, mid][mid + 1, right]
// 2. 左边的个数 + 排序 + 右边的个数 + 排序
ret += mergeSort(nums, left, mid);
ret += mergeSort(nums, mid + 1, right);
// 3. ⼀左⼀右的个数
int cur1 = left, cur2 = mid + 1, i = 0;
while (cur1 <= mid && cur2 <= right) // 升序的时候
{
if (nums[cur1] <= nums[cur2]) {
tmp[i++] = nums[cur1++];
} else {
ret += mid - cur1 + 1;
tmp[i++] = nums[cur2++];
}
}
// 4. 处理⼀下排序
while (cur1 <= mid)
tmp[i++] = nums[cur1++];
while (cur2 <= right)
tmp[i++] = nums[cur2++];
for (int j = left; j <= right; j++)
nums[j] = tmp[j - left];
return ret;
}
};
The Last
嗯,就是这样啦,文章到这里就结束啦,真心感谢你花时间来读。
觉得有点收获的话,不妨给我点个赞吧!
如果发现文章有啥漏洞或错误的地方,欢迎私信我或者在评论里提醒一声~