归并分治_翻转对
原理:
1)思考一个问题在大范围上的答案,是否等于,左部分的答案 + 右部分的答案 + 跨越左右产生的答案
2)计算“跨越左右产生的答案”时,如果加上左、右各自有序这个设定,会不会获得计算的便利性
3)如果以上两点都成立,那么该问题很可能被归并分治解决
4)求解答案的过程中只需要加入归并排序的过程即可,因为要让左、右各自有序,来获得计算的便利性
2.翻转对
给定一个数组 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 Code02_ReversePairs {
public static int MAXN = 50001;
public static int[] help = new int[MAXN];
public static int reversePairs(int[] arr) {
return counts(arr, 0, arr.length - 1);
}
// 统计l...r范围上,翻转对的数量,同时l...r范围统计完后变有序
// 时间复杂度O(n * logn)
public static int counts(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
int m = (l + r) / 2;
return counts(arr, l, m) + counts(arr, m + 1, r) + merge(arr, l, m, r);
}
public static int merge(int[] arr, int l, int m, int r) {
// 统计部分
int ans = 0;
for (int i = l, j = m + 1; i <= m; i++) {// i(左侧位置的数)的循环
while (j <= r && (long) arr[i] > (long) arr[j] * 2) {// j的循环 1.位置的限制 2.大小的限制
j++;
}
ans += j - m - 1;// 计算走过的个数
}
// 正常merge
int i = l;
int a = l;
int b = m + 1;
while (a <= m && b <= r) {
help[i++] = arr[a] <= arr[b] ? arr[a++] : arr[b++];
}
while (a <= m) {// a没到右边边界
help[i++] = arr[a++];
}
while (b <= r) {// b没到左边边界
help[i++] = arr[b++];
}
for (i = l; i <= r; i++) {
arr[i] = help[i];// 记得复制回原数组
}
return ans;
}
}