求一个数组的小和

问题描述

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例:求数组[1,3,4,2,5]的小和。

1左边比1小的数,没有;3左边比3小的数,1;4左边比4小的数,13;2左边比2小的数,1;5左边比5小的数,1342;所以小和为1 + 1+3 + 1 + 1+3+4+2=16

思路概括

     * 利用归并排序的思想可以降低时间复杂度
     * 求小和的逆向思维,举例来说:小和的算法等价于找出1右边比1大的数的个数是4,则整个数组的小和增加1*4;3的右边有2个数比3大,则整个数组的小和增加3*2;4的右边比4大的有1个数,则整个数的小和增加4*1;2的右边比2大的数有1个,则小和增加2*1,5的右边没有比5大的数。
     * 求一个数的右边有多少个数比它大,就可以用归并排序的思路来完成
     * 采用递归的方式,在归并的过程中,一边排序,一边计算小和

代码实现

public static int smallSum(int[] arr){
		//数组长度小于2时,小和为0
		if(arr == null || arr.length < 2){
			return 0;
		}
		return process(arr, 0, arr.length-1);
	}

	/**
	 * 求小和的主体实现部分
	 */
	private static int process(int[] arr, int L, int R) {
		if(L == R){
			return 0;  //如果只有一个数,则不会产生小和
		}
		int sum = 0;  //用于累加小和
		int mid = L + ((R - L) >> 1);  //中间位置
		sum += process(arr, L, mid);  //累加左侧排序求小和的结果
		sum += process(arr, mid+1, R); //累加右侧排序求小和的结果
		sum += merge(arr, L, mid, R);  //累加归并左右两部分时产生小和的结果
		return sum;
	}

	/**
	 * 关键:既要排序,又要求小和,
	 * 并且当左组与右组的元素值相等时,一定要先拷贝右组的值(即让右指针先移动),这也是与标准的mergeSort的区别
	 * 这样才能确定产生的小和个数。
	 * 为什么需要排序:因为在合并的过程中排序后,在下一次合并计算小和的时候更加方便,且不会遗漏重复。
	 */
	private static int merge(int[] arr, int L, int mid, int R) {
		int sum = 0; //用于累加小和
		int help[] = new int[R - L + 1];
		int p1 = L;
		int p2 = mid+1;
		int i = 0; //help的下标指针
		while(p1<=mid && p2 <=R){  //当两个下标都没有越界
			/*if(arr[p1] < arr[p2]){  //当左边部分的数小于右边部分的数时,产生小和arr[p1] * (R-p2+1)
				help[i++] = arr[p1];
				sum += arr[p1] * (R - p2 + 1);
				p1++; //左指针右移
			}else{//如果右组的数小于等于左组的数,则不产生小和,只是复制该值,并将右指针右移
				help[i++] = arr[p2++];
			}*/
			
			//以上的代码的优化
			sum += arr[p1] < arr[p2] ? arr[p1] * (R - p2 + 1) : 0;
			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
			
		}
		//以下两个情况,至多存在一种
		while(p1 <= mid){
			help[i++] = arr[p1++];
		}
		while(p2 <= R){
			help[i++] = arr[p2++];
		}
		
		for(int j=0; j<help.length; j++){
			arr[L+j] = help[j];
		}
		return sum;
	}
	
	public static void main(String[] args) {
		int[] arr = {3,3,2,6,1,9,4,7,11,8,10,5};
		System.out.println(smallSum(arr));
		for(int num : arr){
			System.out.print(num + " ");
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值