「这是我参与2022首次更文挑战的第30天,活动详情查看:2022首次更文挑战」。
一、题目一
在一个数组中, 对于每个数num,求有多少个后面的数 * 2 依然<num,求总个数 比如:[3,1,7,0,2] 3的后面有:1,0 1的后面有:0 7的后面有:0,2 0的后面没有 2的后面没有 所以总共有5个
1、分析
右侧数乘2比左边数小,有多少个数达标(num的右边有多少个数*2后依然小于num)
依然是merge的过程,
不回退的技巧,因为左边有序,右边有序,必然不回退
2、实现
public static int reversePairs(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
public static int process(int[] arr, int L, int R) {
if (L == R) {
return 0;
}
int M = L + ((R - L) >> 1);
return process(arr, L, M) + process(arr, M + 1, R) + merge(arr, L, M, R);
}
public static int merge(int[] arr, int L, int M, int R) {
// [L....M] [M+1....R]
int ans = 0;
// 目前囊括进来的数,是从[M+1, windowR)
int windowR = M + 1;
for (int i = L; i <= M; i++) { // 左边部分
while (windowR <= R && (long) arr[i] > ((long) arr[windowR] * 2)) {
windowR++;
}
ans += windowR - M - 1;
}
int[] help = new int[R - L + 1];
int p1 = L;
int p2 = M + 1;
int index = 0;
while (p1 <= M && p2 <= R) {
help[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= M) {
help[index++] = arr[p1++];
}
while (p2 <= R) {
help[index++] = arr[p2++];
}
for (int i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return ans;
}
二、题目二
给定一个数组arr,两个整数lower和upper,返回arr中有多少个子数组的累加和在[lower,upper]范围上
1、分析
前置知识:数组前缀和的应用,对外提供一种操作(getSum(arr, i, j) ),这种操作特别频繁,需要时间复杂度O(1),任何i到j位置的累加和是多少
sum[i...j] = sum[0...j] - sum[0...i-1]
子数组必须是连续的
暴力解分析:时间复杂度O(N³)
暴力解优化,利用提前生成好的数组前缀和,替代每次遍历验证,可以优化为O(N²)
不够,还有更优化的解法!
换个思路,任何子数组都有结尾树枝,所以任何以i为结尾的子数组,有几个达标(在[lower,up]范围上),最后的结果 = 所有达标的个数累加起来
arr[i...j]在[lower,up]范围上是达标的,等价于 arr[0...j] - arr[0...i-1]在[lower,up]范围上也是达标的
假设0~i整体累加和是x,题目[lower,up],求必须以i位置结尾的子数组,有多少个在[lower,up]范围上,等同于,去求i之前的所有前缀和中有多少个前缀和在[x-up,x-lower]上
2、实现
public static int countRangeSum(int[] nums, int lower, int upper) {
if (nums == null || nums.length == 0) {
return 0;
}
long[] sum = new long[nums.length];
sum[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
sum[i] = sum[i - 1] + nums[i];
}
return process(sum, 0, sum.length - 1, lower, upper);
}
public static int process(long[] sum, int L, int R, int lower, int upper) {
if (L == R) {
return sum[L] >= lower && sum[L] <= upper ? 1 : 0;
}
int M = L + ((R - L) >> 1);
return process(sum, L, M, lower, upper) + process(sum, M + 1, R, lower, upper)
+ merge(sum, L, M, R, lower, upper);
}
三、总结
题目一:
- 不回退技巧
- 数组中只要涉及到左右大小问题,可以利用归并排序merge过程,进一步解决
题目二:
mergeSort改下,有点难!!!
- 前缀和搞事情
- 原[lower,upper] 转化为 前缀和数组x,求[x-upper,x-lower]
- sum[]上,x之前有多少个在[x-upper,x-lower]范围上
- 归并排序,mergeSort上改
有序表解的话,好理解!!!