本文已参与「新人创作礼」活动,一起开启掘金创作之路。
题目来源:https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/
大致题意:
给定一个数组,求出数组中逆序对的个数,逆序对的定义为:
若有两个升序数组 nums1 和 nums2,那么如何比较这两个升序数组合并后的逆序对个数?
使用双指针 i 和 j 分别表示两个升序数组的遍历位置
若 nums1[i] 大于 nums2[j],那么 i 至前一个数组末尾所有元素都大于 nums2[j],这些元素都与 nums2[j] 构成逆序对,统计个数并后移指针 j;
若 nums1[i] 不大于 nums2[j],那么指针 i 后移
通过上述方法可以统计两个升序数组的逆序对个数,那么若两个数组不是升序数组呢?
即若有两个数组 nums1 和 nums2,那么如何比较这两个数组合并后的逆序对个数?
于是就可以使用归并排序的思路,在归并排序的同时计算逆序对个数
具体看代码:
public int reversePairs(int[] nums) {
int n = nums.length;
return merge(nums, new int[n], 0, n - 1);
}
/**
* 归并排序,返回 [left, right] 区间内逆序对个数
* @param nums
* @param temp
* @param left
* @param right
* @return
*/
public int merge(int[] nums, int[] temp, int left, int right) {
if (left >= right) {
return 0;
}
int mid = (left + right) >> 1;
// 归并排序左半
int leftCount = merge(nums, temp, left, mid);
// 归并排序右半
int rightCount = merge(nums, temp, mid + 1, right);
// 左半最大元素不大于右半最小元素,证明两个区间合并后不会出现新的逆序对
if (nums[mid] <= nums[mid + 1]) {
return leftCount + rightCount;
}
// 合并左右区间并计算合并产生的逆序对
int crossCOunt = mergeAndCount(nums, temp, left, right);
return leftCount + rightCount + crossCOunt;
}
/**
* 合并两个区间并计算合并产生的逆序对
*
* @param nums
* @param temp
* @param left
* @param right
* @return
*/
public int mergeAndCount(int[] nums, int[] temp, int left, int right) {
// 先将合并区间元素拷贝到临时数组
for (int i = left; i <= right; i++) {
temp[i] = nums[i];
}
// 左右区间边界
int mid = (left + right) >> 1;
// 做右指针
int leftIdx = left;
int rightIdx = mid + 1;
// 逆序对个数
int count = 0;
// 合并区间
for (int i = left; i <= right; i++) {
if (leftIdx == mid + 1) {
nums[i] = temp[rightIdx++];
} else if (rightIdx == right + 1) {
nums[i] = temp[leftIdx++];
} else if (temp[leftIdx] <= temp[rightIdx]) {
nums[i] = temp[leftIdx++];
} else { // 左指针当前元素大于右指针当前元素,统计逆序对个数
count += mid - leftIdx + 1;
nums[i] = temp[rightIdx++];
}
}
return count;
}