利用归并排序解决小和问题

小和问题:

          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));
	}
}

逆序对也是这个思想解决!

在一个数组中,左边的数如果比右边的数大,则折两个数构成一个逆序对,请打印所有逆序对。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值