给定一个数组,求出数组每个位置的数的左边比它小的值之和。
比如给定数组【1,3,4,2,5】;
1的左边小于1的没有;
3左边小于3的为1;
4左边小于4的为1、3;
2左边小于2的为1;
5左边小于5的为1、3、4、2;
最后求和得到值为16。
下面直接上最优解:
求左侧比它小的数的和,等同于求右侧比它大的数的个数 * 其值。
1的右侧比它大的有4个 * 1 +
3的右侧比它大的有2个 * 3 +
4的右侧比它大的有1个 * 4 +
2的右侧比它大的有1个 * 2 +
5的右侧比它大的有0个 * 5
= 16
代码实现如下:
public static int minSum(int[] data, int l, int r) {
if (l == r) {
return 0;
}
//求中间坐标
int m = l + (r - l >> 1);
return minSum(data, l, m) +
minSum(data, m + 1, r) +
mergerMinSum(data, l, r, m);
}
private static int mergerMinSum(int[] data, int l, int r, int m) {
int[] temp = new int[r - l + 1];
int i = 0;
int p1 = l, p2 = m + 1, sum = 0;
while (p1 <= m && p2 <= r) {
//右侧比它大的数的个数 * 它本身之和
sum += data[p1] < data[p2] ? data[p1] * (r - p2 + 1) : 0;
temp[i++] = data[p1] < data[p2] ? data[p1++] : data[p2++];
}
while (p1 <= m) {
temp[i++] = data[p1++];
}
while (p2 <= r) {
temp[i++] = data[p2++];
}
// l左边为已经排序好的数组
int length = temp.length;
for (int j = 0; j < length; j++) {
data[l + j] = temp[j];
}
return sum;
}
类似归并排序,将数组分为左右两部分,然后merger求和。
在merger求和的过程中,左边和右边的比较,如果左边小于则sum+= 值* 右边数的个数;然后将小的那边拷贝到数组。为啥是拷贝小的到临时数组中呢?因为小的已经计算过一遍了,需要到到比较的左右子数组之外。