小和问题:
Q1、什么是小和?
A:在一个数列中,任意元素p左边所有比p小的数之和,即为小和。
Q2:什么是小和问题?
A: 数列中所有元素的小和之和就是小和问题。
暴力破解的话复杂度很明显是O(N^2),这里采用归并排序的思想,即递归和分治的思想解决。
过程:以(1,2,3,4,5,6,7)为例
首先不断对半划分,直到每个序列的元素个数都为1为止;
1 2 3 4 | 5 6 7
1 2 | 3 4 5 6 | 7
1 | 2 3 | 4 5 | 6 7
1. 然后左半边序列{1}与右半边序列{2}进行比较,建立一个与当前两个序列长度之和相等的辅助数组(第一次长度是2);当前是1与2比较, 如果左半边数小于右半边数(1小于2),则返回小和数1,并把1放入辅助数组,此时左边序列没有元素了,则把右边的序列依次放入辅助数组,此时第一次比较结束,辅助数组变为新的左半边序列{1,2},同步完成的还有序列{3}和序列{4}的比较和{5}和{6}的比较,返回的小和数是3和5;
2. 第二次左半边序列{1,2}与右半边序列{3,4}进行比较,同样建立长度为2+2的辅助数组,首先是1和3进行比较,1小于3,查看右半边比1小的数有几个这里有2个(3和4),所以返回小和数1*2,并把1放入辅助数组;然后继续比较左半边剩下的数2,发现同样右边比2大的数有2个(3和4),所以返回小和数2*2;此时左半边 已经空了,所以将右半边的数依次放入辅助数组,形成新的左半边序列{1,2,3,4},同步完成的还有{5,6}和{7}的比较,返回的小和数是5*1与6*1,生成新的右边序列是{5,6,7}
3. 第三次左半边序列{1,2,3,4}和右半边{5,6,7}进行比较,建立长度为4+3的辅助数组,首先是1和右边进行比较,发现大于1的有3个,所以返回1*3,并把1放入辅助数组;同样2返回的是2*3,3返回的是3*3,4返回的是4*3。最后形成的序列是{1,2,3,4,5,6,7}
需要注意的是,每次返回的序列都是有序的。
递归结束!
public class SmallSum {
public static int smallSum(int[] arr){
if(arr == null || arr.length < 2){
return 0;
}
return mergeSort(arr,0,arr.length-1);
}
//将序列不断划分
public static int mergeSort(int[] arr, int L, int R){
if(L == R){
return 0;
}
// int mid = (R + L) >> 1; //效果是(L+R)/2,但是存在L+R溢出的可能,这种写法不安全;所以用L+(R-L)/2不会溢出
int mid = L + ((R - L) >> 1);
return mergeSort(arr,L,mid) + mergeSort(arr,mid+1,R) + merge(arr,L,mid,R);//这里是归并的难点
}
//对序列进行对比,返回小和数
public static int merge(int[] arr, int L, int mid, int R){
int[] help = new int[R-L+1];
int p1 = L;
int p2 = mid + 1;
int i = 0;
int result = 0;//用来存取当前小和
while(p1 <= mid && p2 <= R){
// if(arr[p1] < arr[p2]){
// result += arr[p1] * (R-p2+1) ;
// help[i++] = arr[p1++];
// }else{
// help[i++] = arr[p2++];
// }
//上述的效果可以两句话解决
result += arr[p1] < arr[p2] ? arr[p1] * (R-p2+1) : 0;
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
//下面两种越界的情况只会出现其中一种
while(p2 <= R){ //说明左边的序列已经为空了,把右边的依次放入help
help[i++] = arr[p2++];
}
while(p1 <= mid){ //说明右边的序列已经为空了,把左边的依次放入help
help[i++] = arr[p1++];
}
//把当前的help数组更新到源数组中
for(i = 0; i < help.length; i++){
arr[L+i] = help[i];
}
return result;
}
public static void main(String[] args) {
int[] arr = {1,3,4,2,5};
System.out.println(smallSum(arr));
}
}
逆序对也是这个思想解决!
在一个数组中,左边的数如果比右边的数大,则折两个数构成一个逆序对,请打印所有逆序对。