题目
在一个数组中,一个数左边比它小的数组的总和,叫数的小和;
所有数的小和累加起来,叫做数组的小和;
求一个数组的小和
例如:[1,3,4,2,5]
1->0(左边没有比1小的数)
3->1(左边 1 比3小)
4->1,3(左边 1,3 比4小)
2->1(左边 1 比2小)
5->1,3,4,2(左边有 1,3,4,2 比5小) 数组小和为 1+1+3+1+1+3+4+2 = 16
题解
- 将寻找计数过程整合在归并排序的过程中
- 同样,在每次merge时定义与合并数组等大的新数组
- 在每次merge过程中会产生小和
- 定义与mergeSize*2数组等大的数组
- 若左组当前值 < 右组当前值,那么将左组当前值放入新数组,计算右组当前值的右侧有几个数,则小和 += 左组当前值*数量
- 若左组当前值 >= 右组当前值,那么将右组值放入新数组,小和不增加
代码
public class SmallSum {
//返回值即为当前数组范围内的小和
public static int process(int[] array,int L,int R){
//数组判空
if (array == null || array.length < 2){
return 0;
}
if (L==R){ //base case
return 0;
}
//计算中点値
int mid = L + ((R-L)>>1);
//分别排好左边和右边,再合并
return process(array,L,mid) //左侧merge时产生的小和
+ process(array,mid+1,R) //右侧merge时产生的小和
//合并左右两边,有序
+ merge(array,L,mid,R);
}
private static int merge(int[] array, int L, int mid, int R) {
//定义p1指针,指向左部分左边界
int p1 = L;
//定义p2指针,指向右部分左边界
int p2 = mid + 1;
//定义同样长度的数组,记录排序后的值
int[] arr = new int[R - L + 1];
int i = 0;
//定义返回结果
int result = 0;
//判断p1,p2有没有越界
while (p1 <= mid && p2 <= R) {
//计算当前值在当前范围的小和
result += array[p1] < array[p2] ? array[p1]*(R-p2+1) : 0;
//左组当前值 < 右组当前值时,左组数放入新数组
arr[i++] = array[p1] < array[p2] ? array[p1++] : array[p2++];
}
//如果p1越界,则将p2后面的数都加入新数组
while (p2 <= R) {
arr[i++] = array[p2++];
}
//如果p2越界,则将p1后面的数都加入新数组
while (p1 <= mid) {
arr[i++] = array[p1++];
}
//将新数组赋给传入数组
for (int j = 0; j < arr.length; j++) {
array[L+j] = arr[j];
}
return result;
}
public static void main(String[] args) {
int[] array = new int[]{1,2,5,8,97,9,6,1};
int smallSum = process(array, 0, array.length - 1);
System.out.println(smallSum);
for (int a : array){
System.out.println(a);
}
}
}
本质:求小和其实是 -> 某个数在逐步扩大的右组范围上寻找比自己大的数字的个数(扩大右组范围时,已经固定的就会进入合并后的新左组,不会再计入)