文章目录
1.题目
493. 翻转对
给定一个数组 nums
,如果 i < j
且 nums[i] > 2*nums[j]
我们就将 (i, j)
称作一个重要翻转对。
你需要返回给定数组中的重要翻转对的数量。
示例 1:
**输入**: [1,3,2,3,1]
**输出**: 2
示例 2:
**输入**: [2,4,3,5,1]
**输出**: 3
2.思路
分治过程中,我们可以顺便统计翻转对的数量,统计跨越左右子数组的翻转对
在合并左右子数组之前,我们需要找出那些一个元素在左子数组,另一个元素在右子数组的翻转对。具体做法是,使用两个指针分别遍历左右子数组,如果左子数组的当前元素 nums[i]
大于右子数组当前元素 nums[j]
的两倍,那就说明从 i
到左子数组末尾的所有元素都和 nums[j]
构成翻转对,我们把这些翻转对的数量累加到总的计数中,然后右子数组的指针 j
向后移动一位;如果不满足条件,就把左子数组的指针 i
向后移动一位。
把左右子数组合并成一个有序的数组。在合并的过程中,我们比较左右子数组的元素大小,将较小的元素依次放入临时数组中,直到左右子数组的元素都处理完。最后,把临时数组中的元素复制回原数组。
3.代码
#include <vector>
class Solution {
public:
// 归并排序函数,用于统计数组中翻转对的数量
// nums 是待处理的数组
// left 是当前处理区间的左边界
// right 是当前处理区间的右边界
// tmp 是用于辅助归并操作的临时数组
int MergeSort(std::vector<int>& nums, int left, int right, std::vector<int>& tmp) {
// 递归终止条件:如果左边界大于等于右边界,说明当前区间为空或者只有一个元素,不存在翻转对,返回 0
if (left >= right)
return 0;
// 计算当前区间的中间位置,用于将数组划分为左右两个子数组
int mid = left + (right - left) / 2;
// 递归调用 MergeSort 函数,分别计算左子数组和右子数组中的翻转对数量
// 并将它们的结果累加到 count 中
int count =
MergeSort(nums, left, mid, tmp) +
MergeSort(nums, mid + 1, right, tmp);
// 统计跨越左右子数组的翻转对数量
int left1 = left, right1 = mid;
int left2 = mid + 1, right2 = right;
// 遍历左右子数组,寻找满足翻转对条件的元素对
while (left1 <= right1 && left2 <= right2) {
// 为了避免乘法运算时出现整数溢出,将 nums[left1] 转换为 long long 类型
if ((long long)nums[left1] > 2LL * nums[left2]) {
// 如果满足 nums[left1] > 2 * nums[left2],说明从 left1 到 right1 的所有元素都与 nums[left2] 构成翻转对
// 因此将这些翻转对的数量(right1 - left1 + 1)累加到 count 中
count += right1 - left1 + 1;
// 右子数组指针后移,继续检查下一个元素
left2++;
} else {
// 不满足条件,左子数组指针后移,继续检查下一个元素
left1++;
}
}
// 重置指针,准备进行归并操作,将左右子数组合并为一个有序数组
left1 = left, right1 = mid;
left2 = mid + 1, right2 = right;
// 记录临时数组的当前索引
int index = left;
// 比较左右子数组的元素,将较小的元素依次放入临时数组中
while (left1 <= right1 && left2 <= right2) {
if (nums[left1] <= nums[left2]) {
// 左子数组的当前元素较小,将其放入临时数组,并移动左子数组指针和临时数组索引
tmp[index++] = nums[left1++];
} else {
// 右子数组的当前元素较小,将其放入临时数组,并移动右子数组指针和临时数组索引
tmp[index++] = nums[left2++];
}
}
// 处理左子数组中剩余的元素,将它们依次放入临时数组
while (left1 <= right1) {
tmp[index++] = nums[left1++];
}
// 处理右子数组中剩余的元素,将它们依次放入临时数组
while (left2 <= right2) {
tmp[index++] = nums[left2++];
}
// 将临时数组中的元素复制回原数组,使原数组在当前区间内有序
for (int i = left; i <= right; ++i) {
nums[i] = tmp[i];
}
// 返回当前区间内的翻转对总数
return count;
}
// 计算数组中翻转对的总数
// nums 是待处理的数组
int reversePairs(std::vector<int>& nums) {
// 创建一个与原数组大小相同的临时数组,用于辅助归并操作
std::vector<int> tmp(nums.size());
// 调用 MergeSort 函数,对整个数组进行处理,并返回翻转对的总数
return MergeSort(nums, 0, nums.size() - 1, tmp);
}
};