问题描述
一个数列,其中一个数p,其左边所有比p小的数的和,是数p的小和。
求这个数列所有数的小和之和。
例子:
[3, 1, 4, 5, 2]
3左边比3小的数:
1左边比1小的数:
4左边比4小的数:3, 1
5左边比5小的数:3, 1, 4
2左边比2小的数:1
把这些数求和得到的就是小和: 13
解法
(1)暴力解法,遍历。T=O(n^2)
(2)归并排序
复习归并排序:
a.首先划分,一直划分到不能划分,即每个组都只有一个数值。
b.然后合并,合并的过程就是每个二划分排序的过程。
c.在合并的时候,开辟一个辅助数组,其大小等于这两个合并数列的大小。
d.设置两个指针分别指向每个数列的首部,然后比较得到其中较小的值,并将这个值放入辅助数组中。然后取出小值的那个数列的指针可以继续向前走,与另一个数列的指针所指向的值继续比较
e.这样比较完成后,如果两个数列中有个数列的数值有剩余,即其指针没有走到末尾,则将这个数列直接赋到辅助数组末尾即可。
f.然后将辅助数组中的值拷贝回原数组中刚才合并的那两个数列的位置上。
思路:
在归并排序的合并(combine)阶段(即上面归并排序的步骤d)进行小和问题的计算。
指针p1和p2指向两个划分后排好序的数组。p1指向的是左边的数组(有位置信息),如果a[p1] < a[p2],(此时小和问题的两个条件都满足了:“左边”,“小”),也就是说,p2指针后面的数也比a[p1]大(因为是已经排好序的),一共有 (r-p2+1)个。(r表示数组最右端)。
所以,计算公式为:
count += a[p1] < a[p2] ? (r - p2 + 1)*a[p1] : 0;
注:
计算小和问题,要乘a[p1]。因为要求的不是比p小的个数,而是比p小的数的和。
如果要求的是个数(其实也就是扩展归并排序的另一应用:逆序对问题(大小反过来的)) 则:
count += a[p1] < a[p2] ? (r - p2 + 1) : 0;
代码
代码直接copy了别人的,C/C++写的,感觉不错
#include <iostream>
#include <vector>
using namespace std;
/*
2017/11/12
利用归并排序求小和
*/
#if 1
int merge(vector<int>&a, int l, int mid, int r)
{
int count = 0;
int p1 = l;
int p2 = mid + 1;
int i = 0;
vector<int>help(r - l + 1);
while (p1 <= mid && p2 <= r)
{
count += a[p1] < a[p2] ? (r - p2 + 1)*a[p1] : 0;
help[i++] = a[p1] < a[p2] ? a[p1++] : a[p2++];
}
while (p1 <= mid)
help[i++] = a[p1++];
while (p2 <= r)
help[i++] = a[p2++];
for (i = 0; i < help.size(); i++)
a[l + i] = help[i];
return count;
}
int mergeSort(vector<int>&a, int l, int r)
{
if (l == r)
return 0;
int mid = l + (r - l) / 2;
return mergeSort(a, l, mid) + mergeSort(a, mid + 1, r) + merge(a, l, mid, r);
}
int SmallSum(vector<int>a)
{
if (a.size() < 2)
return 0;
return mergeSort(a, 0, a.size() - 1);
}
void main()
{
vector<int>a = { 7, 2, 14, 6, 14 };
cout << SmallSum(a) << endl;
system("pause");
}
#else
#endif
参考链接:
https://blog.csdn.net/zhzh402/article/details/80266663
https://blog.csdn.net/liuxiao214/article/details/78530993
https://blog.csdn.net/m0_37323771/article/details/79842263