【题目】
数组小和的定义如下:
例如:数组s = [1, 3, 5, 2, 4, 6],在s[0]的左边小于或者等于s[0]的数的和为0,在s[1]的左边小于或等于s[1]的数的和为1……将所有位置的左边比它小或者等于的数的和相加起来就是小和。
给定一个数组,实现函数返回s的小和。
【基本思路】
最简单易懂的方法,就是依次遍历数组每一个位置 i,然后再遍历一次找到 i 左边所有小于或者等于 s[i] 的数,累加即可。该时间复杂度为O(N^2)。
我们可以换一种思路考虑这个问题,对于每一个位置 i 的值s[i],我们计算它比右边的几个数小或者等于,假设这样的数有 num 个,那么在最后的小和中,s[i]提供的值的总和就是s[i] * num。显而易见,将每一个位置的 s[i] * num 加起来就是最终的小和。
使用归并排序的过程可以达到这个目的,试想,在归并排序的组间合并过程中,左右两组都已经有序。假设左组为left[],右组为right[],并分别从位置 i 和 j 开始比较。如果left[i] <= right[j],可以肯定right数组 j 位置右边的所有位置(假设有n个)的值都一定比left[i]大或等于,所以会产生一个小和left[i] * n。当然这时的n不是原数组中所有在left[i]右边比left[i]大或等于的数,因为left和right只是原数组中的两个子数组。
整个归并过程该怎么进行就怎么进行,排序过程没有任何变化,只是在组间合并的时候计算所有产生的小和并累加起来就是最终的结果。整个过程时间复杂度O(NlogN)。
下面是用python3.5实现的代码。
def getSmallSum(arr):
def mergeSort(arr, start, end):
if start == end:
return 0
mid = (start + end) // 2
return mergeSort(arr, start, mid) + mergeSort(arr, mid+1, end) + merge(arr, start, mid, end)
def merge(arr, start, mid, end):
left = start
right = mid + 1
res = []
sum = 0
while left <= mid and right <= end:
if arr[left] < arr[right]:
res.append(arr[left])
sum += arr[left] * (end - right + 1)
left += 1
else:
res.append(arr[right])
right += 1
res += arr[left : mid+1]
res += arr[right : end+1]
for i in range(start, end+1):
arr[i] = res.pop(0)
return sum
if arr == None or len(arr) == 0:
return 0
return mergeSort(arr, 0, len(arr)-1)