给一个数组,对每个元素计算数组左边比当前元素小的元素和,最后将这些结果再求和。例如{1, 3, 5, 2, 4, 6}
的小和为1+4+1+6+15=27
。
这个题实际上是逆序数的变形,以元素3来说,后面的5,4,6比3大,所以小和都会将其计算一次,所以我们想要计算是对于任意元素i,右边比i大的元素个数。考虑归并排序能将左右两个有序数组合并,对于左边的任意元素i,都能计算得到右边比i大的元素个数。每一轮合并都能计算的到元素i右边比其大的元素个数,经过所有合并之后,就得能得到原数组中在i右边且比i大的元素个数。
- 为什么不考虑左边比i大的元素?防止重复计算,因为最初是从长度为1的数组开始合并的,所以左边比i大的元素都已经计算过一次了。
public class SmallSumMerge {
public static void main(String[] args) {
int[] arr = {1, 3, 5, 2, 4, 6};
System.out.println(smallSum(arr));
}
private static int smallSum(int[] arr) {
if (arr == null || arr.length <= 1) {
return 0;
}
int[] temp = new int[arr.length];
return mergeSort(arr, 0, arr.length, temp);
}
private static int mergeSort(int[] source, int fromIndex, int toIndex, int[] temp) {
if (fromIndex + 1 >= toIndex) {
return 0;
}
int mid = (fromIndex + toIndex) >> 1;
int sum = mergeSort(source, fromIndex, mid, temp);
sum += mergeSort(source, mid, toIndex, temp);
return sum + merge(source, fromIndex, toIndex, temp);
}
private static int merge(int[] source, int fromIndex, int toIndex, int[] temp) {
int mid = (fromIndex + toIndex) >> 1;
int leftIndex = fromIndex, rightIndex = mid;
int tempIndex = fromIndex;
int sum = 0;
while (leftIndex < mid && rightIndex < toIndex) {
if (source[leftIndex] <= source[rightIndex]) {
temp[tempIndex++] = source[leftIndex];
sum += source[leftIndex] * (toIndex - rightIndex);
leftIndex++;
} else {
temp[tempIndex++] = source[rightIndex++];
}
}
int restIndex = leftIndex < mid ? leftIndex : rightIndex;
while (tempIndex < toIndex) {
temp[tempIndex++] = source[restIndex++];
}
// copy back to source
for (int i = fromIndex; i < toIndex; i++) {
source[i] = temp[i];
}
return sum;
}
}