在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。
例子: [1,3,4,2,5]
1左边比1小的数:没有
3左边比3小的数:1
4左边比4小的数:1、3
2左边比2小的数:1
5左边比5小的数:1、3、4、 2
所以数组的小和为1+1+3+1+1+3+4+2=16
思路: 一个数左边比他小的总和=一个数右边所有比他大的总和
归并中 merge方法 主要是针对左边所有数据 和 右边所有数据的合并
那么如果左边比右边小,那么右边的指针到最右距离乘以 你这个左边小的值 就是 一个数右边所有比他大的总和
所有的值累加一块就是最终解。
public static int process(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
// l < r
int mid = l + ((r - l) >> 1);
return
process(arr, l, mid)
+
process(arr, mid + 1, r)
+
merge1(arr, l, mid, r);
}
public static int merge1(int[] arr, int L, int m, int r) {
int[] help = new int[r - L + 1];
int i=0;
int p1=L;
int p2=m+1;
int ans=0;
while (p1<=m&&p2<=r){
ans+=arr[p1]<arr[p2]?arr[p1]*(r-p2+1):0;
help[i++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (int i1 : help) {
arr[L+i]=help[i1];
}
return ans;
}
在一个数组中,
任何一个前面的数a,和任何一个后面的数b,
如果(a,b)是降序的,就称为逆序对
返回数组中所有的逆序对
比如 2 41 3 输出 (21)(41)(43)
思路:此题求的是右边哪些数比这个数小
和上到题套路相反,归并求降序就行了
public static int process(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
// l < r
int mid = l + ((r - l) >> 1);
return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l,
mid, r);
}
public static int merge(int[] arr, int L, int m, int r) {
int[] help = new int[r - L + 1];
int i = help.length - 1;
int p1 = m;
int p2 = r;
int res = 0;
while (p1 >= L && p2 > m) {
res += arr[p1] > arr[p2] ? (p2 - m) : 0;
help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--];
}
while (p1 >= L) {
help[i--] = arr[p1--];
}
while (p2 > m) {
help[i--] = arr[p2--];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return res;
}
在一个数组中,
对于每个数num,求有多少个后面的数 * 2 依然<num,求总个数
比如:[3,1,7,0,2]
3的后面有:1,0
1的后面有:0
7的后面有:0,2
0的后面没有
2的后面没有
所以总共有5个
这道题就是归并算法和要求的分开做就行了,左边的指针和右边的2倍比较如果符合,右边的就移动,看移动多少个位置就记录
public static int process(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
// l < r
int mid = l + ((r - l) >> 1);
return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, 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 i = 0;
int p1 = L;
int p2 = m + 1;
while (p1 <= m && p2 <= r) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return ans;
}
https://leetcode.com/problems/count-of-range-sum/
给定一个数组arr,两个整数lower和upper,
返回arr中有多少个子数组的累加和在[lower,upper]范围上
思路:假设0-i前缀和为X,我们要求累加和在[lower,upper]范围上,可以转化为假如以i结尾,0-i-1范围前缀和在X-up到X-low之间的数
而这一块就是利用归并的merge来做到,那么为啥归并能结合前缀树解决这个问题呢?
初始归并:
此时只会有0或1个元素,不涉及左右两段的情况,是可以的
合并归并:
此时是有左右两段的,左右两段是分别有序的,对前缀和数组排序并不会修改数组中元素的值,只是改变了元素是位置,如对left~right=3~5位置的前缀和排序,排序后前缀和3~5位置的数还是原来3~5位置的数,只是排列变化了
你在merge中 假如 你右边有个值为8 题目要求(1-2)那么你就是找左边6-7的值,假如找到一个6符合,也就是说明 这个6代表的0-X和8代表的0-Y 之间X到Y是在(1-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);
}
public static int merge(long[] arr, int L, int M, int R, int lower, int upper) {
int ans = 0;
int windowL = L;
int windowR = L;
// [windowL, windowR)
for (int i = M + 1; i <= R; i++) {
long min = arr[i] - upper;
long max = arr[i] - lower;
while (windowR <= M && arr[windowR] <= max) {
windowR++;
}
while (windowL <= M && arr[windowL] < min) {
windowL++;
}
ans += windowR - windowL;
}
long[] help = new long[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
while (p1 <= M && p2 <= R) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return ans;
}
总结:如果我们求什么东西,就是左边和右边的和啊,左边和右边的有一定关系的这种,就往merge靠拢,这样思路就会清晰