1. 题目
参考链接: 计算数组的小和
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例子:
[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
要求时间复杂度O(NlogN),空间复杂度O(N)
2. 题解
解法1: 归并排序
最容易想到的做法是遍历一遍数组,对于每个元素计算前面比它小的数的和,累加即可得出结果,时间复杂度是O(N²)。但这不符合题目要求
smallSum([1,3,4,2,5])
实际就等于``smallSum([1,3,4])+smallSum([2,5])+c`。之所以还有个c,是因为左半段数组中可能存在比右半段数组小的元素,这个不能遗漏。通过归并排序的merge过程,我们可以很方便计算这个c。
在merge时,左段数组L和右段数组R都是有序的了。结合下边的示意图,当L[i]<=R[j]
时,表示L[i]
比R[j]~R[r]
的元素都要小。因此c需加上j及以后元素的个数*L[i],即c+=(r-j+1) * L[i]
。
如下图所示
Java代码如下: (归并排序代码可参考 算法—LeetCode 912. 排序数组 (多种排序算法总结))
public class GetSmallSum {
class Solution {
int[] temp;
public int getSmallSum(int[] nums) {
temp = new int[nums.length];
return merge(nums, 0, nums.length - 1);
}
/**
* 返回数组的小和
*
* @param nums
* @param left
* @param right
* @return
*/
public int merge(int[] nums, int left, int right) {
if (left >= right) {
return 0;
}
int mid = left + right >> 1;
int leftSum = merge(nums, left, mid);
int rightSum = merge(nums, mid + 1, right);
int curSum = 0; // 当前这次合并的小和, 即 c
int i = left, j = mid + 1;
int k = left;
while (i <= mid && j <= right) {
if (nums[i] < nums[j]) {
curSum = curSum + nums[i] * (right - j + 1);
temp[k++] = nums[i++];
} else {
temp[k++] = nums[j++];
}
}
while (i <= mid) {
temp[k++] = nums[i++];
}
while (j <= right) {
temp[k++] = nums[j++];
}
for (int t = left; t <= right; t++) {
nums[t] = temp[t];
}
return leftSum + rightSum + curSum;
}
}
public static void main(String[] args) {
Solution solution = new GetSmallSum().new Solution();
int[] a = {1, 3, 4, 2, 5};
System.out.println(solution.getSmallSum(a));
}
}