「力扣」第 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
注意:
- 给定数组的长度不会超过
50000。 - 输入数组中的所有数字都在32位整数的表示范围内。
数据范围:
思路一:暴力解法(超时)
public class Solution {
public int reversePairs(int[] nums) {
int len = nums.length;
int count = 0;
for (int i = 0; i < len - 1; i++) {
for (int j = i + 1; j < len; j++) {
if ((long) nums[i] > (long) 2 * nums[j]) {
count++;
}
}
}
return count;
}
}
方法二:分治算法
不可以归并回去,关键的地方是:不断增大 j 好让 i 移动到末尾。
对于每一个 i,找到最小的 j 使得 nums[j] > 2 * nums[i]。
可以用二分查找,也可以遍历。但是这种遍历是不回头的。就直接遍历吧。
例子(以后再展开讲)
4 5 6 7 |1 2 3
j |i
4 5 6 7 |1 2 3
j | i
方法二:归并排序
public class Solution {
public int reversePairs(int[] nums) {
int len = nums.length;
if (len < 2) {
return 0;
}
int[] copy = new int[len];
for (int i = 0; i < len; i++) {
copy[i] = nums[i];
}
int[] temp = new int[len];
return reversePairs(copy, 0, len - 1, temp);
}
private int reversePairs(int[] nums, int left, int right, int[] temp) {
if (left == right) {
return 0;
}
int mid = (left + right) / 2;
int leftPairs = reversePairs(nums, left, mid, temp);
int rightPairs = reversePairs(nums, mid + 1, right, temp);
int crossPairs = mergeAndCount(nums, left, mid, right, temp);
return leftPairs + rightPairs + crossPairs;
}
private int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) {
for (int i = left; i <= right; i++) {
temp[i] = nums[i];
}
// 第 1 步:计算超级逆序数,j 归并回去的时候计算逆序关系
int i = left;
int j = mid + 1;
int count = 0;
while (i <= mid && j <= right) {
// nums[i] > 2 * nums[j] 防止乘 2 溢出,所以进行类型转换
if ((long) temp[i] > 2 * (long) temp[j]) {
// 右边归并回去的时候计算逆序数
count += (mid - i + 1);
j++;
} else {
i++;
}
}
// 第 2 步:这一步让 nums[left..right] 有序
i = left;
j = mid + 1;
for (int k = left; k <= right; k++) {
if (i == mid + 1) {
nums[k] = temp[j];
j++;
} else if (j == right + 1) {
nums[k] = temp[i];
i++;
} else if (temp[i] <= temp[j]) {
nums[k] = temp[i];
i++;
} else {
nums[k] = temp[j];
j++;
}
}
return count;
}
}