求小和问题
问题描述:在一个数组中每个数的左边比该数小的数的累加和称为这个数组的小和,如何给定一个数组求出该数组的小和.
思路:我们只需要找出数组中每个数左边谁比它小 然后把比它小的数记录下来即求出了该数组的小和
举个例子在数组{1,2,4,3,6}中 如果你要求小和,那么是不是先看1左边有没有比1小的数,然后再看2左边有没有比1小的数.....依次遍历直到数组元素都遍历
完成,然后把那些找出来的数累加起来就是小和了,因此这个例子的小和应该是:0+1+1+2+1+2+1+2+4+3=17;
这是通过小和的定义去直接从左往右遍历计算得到的答案,那么我们能不能在时间复杂度的角度上去优化求小和的方式呢?
答:既然我们要求每个数的左边谁比这个数小然后累加起来,那么我们是不是可以转换为求每个数的右边谁比它大,假设3的右边比它大的数有4,5,6
那么是不是当4,5,6求小和的时候一定会加个3(因为3在4,5,6的左边并且3比4,5,6小).
举个例子在数组{1,2,4,3,6}中
1的右边有2,4,3,6比它大 因此1会在2,4,3,6求小和的时候分别加1 所以1*4;
2的右边有4,3,6,比它大 因此2会在4,3,6求小和的时候分别加2 所以2*3;
4的右边有6比它大 因此4会在6求小和的时候加上4 所以1*4;
3的右边6比它大 所以3会在6求小和的时候加上3 所以1*3;
6的右边没有数了,所以停止
所以这个数组的小和等于1*4+2*3+1*4+1*3=4+6+4+3=17;
和我们用定义去求小和得到的答案一样
因此我们求小和问题 我们完全可以转化为求数组中每个元素的右边有几个数比这个元素大来解决,
那么问题来了,怎么求某个元素的右边有几个数比这个元素大?
毋庸置疑当然是归并排序了.
归并排序求小和的原则:
①:只有左组才能产生小和.
②:内部组之间不产生小和
③:当左组的元素和右组的元素相等时,先记录右组.
普通方式实现求小和
public static int trueSmallSum(int[] arr){
int ans=0;
if(arr==null || arr.length<2){
return ans;
}
for (int i = 1; i <arr.length ; i++) {
//遍历arr[i]左边的元素有比arr[i]小的就累加
for(int j=0;j<i;j++){
ans+=arr[j]<arr[i]?arr[j]:0;
}
}
return ans;
}
归并排序实现求小和
public static int seekSmallSum(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){
int[] help=new int[r-l+1];
int index=0;
int p1=l;
int p2=m+1;
int ans=0;
while(p1<=m && p2<=r){
ans += arr[p1] <arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
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;
}
对数器实现
public static int trueSmallSum(int[] arr){
int ans=0;
if(arr==null || arr.length<2){
return ans;
}
for (int i = 1; i <arr.length ; i++) {
for(int j=0;j<i;j++){
ans+=arr[j]<arr[i]?arr[j]:0;
}
}
return ans;
}
public static void printArray(int[] arr){
for (int i : arr) {
System.out.print(i+" ");
}
System.out.println();
}
public static int[] generateRandomArray(int maxLen,int maxValue){
int[] arr=new int[(int)(Math.random()*(maxLen+1))];
for (int i = 0; i < arr.length; i++) {
arr[i]=(int)(Math.random()*(maxValue+1))-(int)(Math.random()*(maxValue+1));
}
return arr;
}
public static int[] copyArray(int[] arr){
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
最终测试
public static void main(String[] args) {
int testTime=1000000;
int maxLen=100;
int maxValue=100;
boolean success=true;
System.out.println("Begin");
LocalDateTime now = LocalDateTime.now();
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxLen, maxValue);
int[] arr2 = copyArray(arr1);
if(seekSmallSum(arr1)!=trueSmallSum(arr2)){
success=false;
printArray(arr1);
printArray(arr2);
}
}
LocalDateTime end = LocalDateTime.now();
Duration between = Duration.between(now, end);
System.out.println((success==true?"Nice!!!":"Fucking fucked")+"耗时:"+between.getSeconds()+"秒"+between.toMillis()%1000+"毫秒");
}