利用归并排序求最小和

题目:在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组
的小和。求一个数组 的小和。
例子:[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

解法:除了向例子上那样求小和,还可以统计一个数的右边有多少比它大的数来求小和,例如:
数组:[1,3,4,2,5]
元素1的右边3,4,3,5四个元素都比它大,说明元素1必然是3、4、2、5这些元素的小和的一部分,故可以得到小和为 1x4 = 4;
元素3的右边4,5元素比它大,说明元素3必然是4、5这些元素的小和的一部分,故可以得到小和为 3x2 = 6;
元素4的右边5元素比它大,说明元素4必然是5这个元素的小和的一部分,故可以得到小和为 4x1 = 4;
元素2的右边5元素比它大,说明元素2必然是5这个元素的小和的一部分,故可以得到小和为 2x1 = 2;
故整个数组小和为:4+6+4+2=16;

采用上面解法能提供效率吗,很遗憾不能,算法复杂度还是O(n^2),但是解法思想可以利用归并排序归并时候去掉统计一个元素右边有多少元素比它大的过程。归并时候数组是有序的,会不会影响求最小和呢?答案是不会。
例如:
1,4,3,9,5
第一步先将其分成 1,4,3 和 9,5

	左边数组最小和:1*2 = 2;
	右边数组最小和:0

非排序求归并最小和:

	现在求合并后最小和:1*2+4*2+3*2=16;

现在是排序归并求最小和

 	1,3,4 和 5,9
 	排序后归并最小和过程如下:
 	元素1看到右边元素为5比它大,就说明右边所有的数组元素都比他大,故:1*2
 	元素3看到右边元素为5比它大,就说明右边所有的数组元素都比他大,故:3*2
 	元素4看到右边元素为5比它大,就说明右边所有的数组元素都比他大,故:4*2

从上面可以看出归并不排序数组求最小和和归并排序后最小和得到归并后最小和是一致的,其中归并排序数组也是归并得到,利用这个性质就可以在归并的过程中求出整个数组最小和。代码如下:

package sort;

public class MinSumDemo {
   // 小和
   /**
    * 在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组
    * 的小和。求一个数组的小和。
    * 例子:[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
    */

   public static void main(String[] args) {
       int[] nums = {1,4,3,9,5};
       int i1 = minSum2(nums);
       int i = minSum(nums);

       System.out.println("minsum:"+i);
       System.out.println("minsum:"+i1);
   }
   // 暴力解法
   public static int minSum2(int[] nums){
       if(nums == null || nums.length <= 1){
           return 0;
       }
       int numSum = 0;
       for(int i=0; i<nums.length-1; i++){
           int target = nums[i];
           int count = 0; // 统计右边比target大的数的个数
           for(int j=i+1; j<nums.length; j++){
               if(nums[j] > target){
                   count++;
               }
           }
           numSum += target*count;

       }
       return numSum;
   }

   /**
    *
    * @param nums
    * @return  数组的小和
    */
   public static int minSum(int[] nums){
       if(nums == null || nums.length <= 1){
           return 0;
       }
       return minSum(nums,0,nums.length-1);
   }

   private static int minSum(int[] nums, int low, int high) {
       if(low < high){
           // 二分
           int mind = low + (high-low)/2;
           int leftMinsum = minSum(nums, low, mind);
           int rightMinsum = minSum(nums, mind+1, high);
           int meregeMinsum = mergeMinsum(nums,low,mind, high);
           return leftMinsum+rightMinsum+meregeMinsum;
       }
       // 如果分到只有一个元素,就返回0,因为一个元素无小和
       return 0;
   }

   private static int mergeMinsum(int[] nums, int llow, int lhigh, int rhigh) {

       int[] newArray = new int[rhigh-llow+1];  // 创建合并数组
       int rlow = lhigh+1;
       int rlength = rhigh-rlow+1;   // 右边数组长度
       // 定义合并数组的指针
       int p1 = llow;
       int p2 = rlow;
       int p = 0;      // 新数组的指针

       int minSum = 0;

       while(p1 <= lhigh && p2 <= rhigh){
           if(nums[p1] < nums[p2]){  // 右边的比左边大
               minSum += nums[p1]*(rlength-(p2-rlow));
               newArray[p++] = nums[p1++];
           }else{
               newArray[p++] = nums[p2++];
           }
       }

       while(p1 <= lhigh){
           newArray[p++] = nums[p1++];
       }
       while(p2 <= rhigh){
           newArray[p++] = nums[p2++];
       }

       //将合并数组赋值到原数组
       for(int i=0;i<newArray.length; i++){
           nums[i+llow] = newArray[i];
       }
       return minSum;

   }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值