求小和问题
- 简述
有一个数组,我们从左往右遍历,每移动到一位就将它左边的所有比它小的数字全部相加,最后的总和记为小和。下面是一个常规做法过程例子:
那么我们需要的是更快的做法。
- 分析逻辑
我们仍然可以使用之前的归并排序,将其中merge的部分进行修改。
一下面为例,仍然是将该数组一分为二
图 1
图 2
仍然是准备两个指标,和一个空列表help
看左边的1,3左边p1指向1右边p2指向3,进行比较,1<3,小和加1,把1存入help列表,指标p1右移出界(左边没有数字了),直接拷贝右边3进入help列表,再把help列表拷贝进数组。(help[1,3])
进入上层,p1指向1,p2指向2,1<2,小和加1,把1存入help列表,p1指向3,3>2,小和加0,把2存入help列表,右边没有数字了,把左边剩下的直接拷贝进help列表(help[1,2,3])
(5,6同理)
进入上一层,此时左边和右边都已经排好序,p1指向1,p2指向5,5>1,(因为右边也是有序的,所有得出5后面的数字都大于1,则直接进行下标计算即可)小和加1*2,把1存入help列表,p1指向2,5>2则小和加2*2,把2存入help列表,以此类推,最后整个数组有序,并求出了该数组的小和。
(ps:1.如果左边的数字大于右边,则将右边的数字存入help列表,p2指标右移一位,也就是谁小谁进入help;2.因为每次都是求完小和后再进行排序,所以排序不会影响结果)
- 代码(Java):
public static int process(int[] arr,int L,int R){
if(L==R){
return;
}
int M = L+((R-L)>>1);
merge(arr,L,M,R);
return process(arr,L,M)+process(arr,M+1,R)+merge(arr, L,M,R);
}
public static int merge(int[] arr,int L,int R){
int p1 = L;
int p2 = M+1;
int[] help = new int[R-L+1];
int res =0; //小和
int i = 0;
while(p1 <= M && p2 <= R){
res += arr[p1] < arr[p2] ? (R - p2+1)*arr[p1]: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 i = 0,i < help.length,i++){
arr[L+i] = help[i]; //把排好序的help拷贝到该次求小和数组范围
return res;
}
}
}
逆序对问题
- 简述
一个数组,左边的数比右边的数大则记为一个逆序对,它的本质和前面的求小和问题一样,我们同样只需要将merge修改一下即可。(求小和是求左边比右边小的)
以上就是该数组中所有的逆序对。