现定义数组单调和为所有元素i的f(i)值之和。这里的f(i)函数定义为元素i左边(不包括其自身)小于等于它的数字之和。请设计一个高效算法,计算数组的单调和。
给定一个数组A同时给定数组的大小n,请返回数组的单调和。保证数组大小小于等于500,同时保证单调和不会超过int范围。
测试样例:
[1,3,5,2,4,6],6
返回:27
import java.util.*;
public class MonoSum {
public int calcMonoSum(int[] A, int n) {
// write code here
return MergeSort(A,0,n-1);
}
public int MergeSort(int[] arr, int startIndex, int endIndex){
int total=0;
if(startIndex < endIndex)
{
int midIndex = (startIndex + endIndex) / 2;
total+=MergeSort(arr, startIndex, midIndex);
total+=MergeSort(arr,midIndex+1, endIndex);
total+=Merge(arr, startIndex, midIndex, endIndex);
}
return total;
}
public int Merge(int[] arr,int start,int mid,int end){
int total=0;
for(int i=mid+1;i<=end;i++){
for(int j=start;j<=mid;j++){
if(arr[i]>=arr[j])
total+=arr[j];
}
}
return total;
}
}
public static int calcMonoSum(int[] A, int n) {
int[] sum = new int[n];
sum[0] = 0;
for (int i = 1; i < n; i++) {
if (A[i] > A[i - 1]) {
sum[i] = sum[i - 1] + A[i - 1];
for (int j = i - 2; j >= 0; j--) {
if (A[j] > A[i - 1] && A[j] <= A[i]) {
sum[i] += A[j];
}
}
} else if (A[i] == A[i - 1]) {
sum[i] = sum[i - 1] + A[i - 1];
} else {
int p = i - 2;
for (; p >= 0 && A[p] > A[i]; p--){}
if (p >= 0 && A[p] == A[i]) {
sum[i] = sum[p] + A[p];
} else if (p >= 0 && A[p] < A[i]) {
sum[i] = sum[p] + A[p];
for (int k = p - 1; k >= 0; k--) {
if (A[k] > A[p] && A[k] <= A[i]) {
sum[i] += A[k];
}
}
}
}
}
int s = 0;
for (int i = 0; i < n; i++) {
s += sum[i];
}
return s;
}
按照DP的思想写的,计算过的一些子问题不重复计算,可是感觉不对
时间复杂度的话应该小于N方,但是自己试验了很多数据,整型并不比暴力法用时短,浮点则比暴力法用时短
import java.util.*;
/*
* @question 现定义数组单调和为所有元素i的f(i)值之和。这里的f(i)函数定义为元素i左边(不包括其自身)小于等于它的数字之和。请设计一个高效算法,计算数组的单调和。
给定一个数组A同时给定数组的大小n,请返回数组的单调和。保证数组大小小于等于500,同时保证单调和不会超过int范围。
测试样例:
* @author snow
* @time 2016-08-06 16:50
* @算法思想很简单不写了
*/
public class MonoSum {
public int calcMonoSum(int[] A, int n) {
int sum = 0;
for(int i=1;i<=n;i++){
sum = sum + getSubSum(Arrays.copyOfRange(A,0,i+1),i);
}
return sum;
// write code here
}
private int getSubSum(int[] B,int n){
int sub = 0;
for(int i:B){
if(i<=B[n]){
sub = sub + i;
}
}
return sub - B[n];
}
}
解题思路:计算数组中每个元素的右边大于等于它的元素个数,有几个,就表示它被加了几次,这样只需定义一个函数,来计算元素右边大于等于它的元素个数即可。
import java.util.*;
public class MonoSum {
public int calcMonoSum(int[] A, int n) {
// write code here
int monSum=0;
if(n==1)
return monSum;
for(int i=0;i<n;i++)
{
int count=calcCount( A,n,i);
monSum=monSum+A[i]*count;
}
return monSum;
}
public int calcCount(int[] A,int n,int index)
{
int count=0;
for(int i=index+1;i<n;i++)
{
if(A[i]>=A[index])
count++;
}
return count;
}
}
class MonoSum {
public:
int calcMonoSum(vector<int> A, int n) {
// write code here
if(n > 500) return -1;
if(n <= 0) return 0;
int sum = 0;
for(int i = 0; i < n - 1; ++i)
{
if(A.at(i) <= A.at(n - 1))
sum += A.at(i);
}
return sum + calcMonoSum(A, n - 1);
}
};
这题利用归并排序的特点:在归并阶段,比如左边{2, 5} 右边{3, 6}。左边的2肯定在3和6的前面,5也肯定在6前面,所以此次归并可以算一个单调和2 * 2 + 5 * 1=9。每次归并都计算就可得到总的和
class MonoSum {
public:
int count = 0;
void merge(vector<int> &arr, int start, int mid, int end) {
vector<int> res;
int left_index = start;
int right_index = mid + 1;
while(left_index <= mid && right_index <= end) {
if(arr[left_index] <= arr[right_index]) {//合并时左边的更小
count += (end - right_index + 1) * arr[left_index];
res.push_back(arr[left_index]);
left_index++;
}
else {
res.push_back(arr[right_index]);
right_index++;
}
}
for(int i = left_index; i <= mid; i++) {
res.push_back(arr[i]);
}
for(int i = right_index; i <= end; i++) {
res.push_back(arr[i]);
}
for(int i = start; i <= end; i++) {
arr[i] = res[i - start];
}
}
void mergeSort(vector<int> &arr, int start, int end) {
int mid = start + (end - start) / 2;
if(mid > start) {
mergeSort(arr, start, mid);
}
if(end > mid + 1) {
mergeSort(arr, mid + 1, end);
}
merge(arr, start, mid, end);
}
int calcMonoSum(vector<int> A, int n) {
// write code here
if(n < 2) {
return 0;
}
mergeSort(A, 0, n - 1);
return count;
}
};
class MonoSum {
public: int result = 0;
int calcMonoSum(vector<int> A, int n) {
if(n<2)
return 0;
MergeSort(A, 0, n-1);
return result;
}
void MergeSort(vector<int> &A, int s, int e){
int mid = (s+e)/2;
if(mid > s)
MergeSort(A, s, mid);
if(mid+1 < e)
MergeSort(A, mid+1, e);
Merge(A, s, mid, e); } void Merge(vector<int> &A, int s, int mid, int e){ vector<int> L(A.size(),0); int i = s; int j = mid+1; int k = s; while(i<=mid && j<=e){ if(A[i] <= A[j]){ result += (e-j+1)*A[i]; L[k++] = A[i++]; }else L[k++] = A[j++]; } while(i <= mid) L[k++] = A[i++]; while(j <= e) L[k++] = A[j++]; for(int i=s;i<=e;i++) A[i] = L[i]; }
};
#!/usr/bin/env python
#-*- coding:utf8 -*-
# -*- coding:utf-8 -*-
class MonoSum:
def calcMonoSum(self, A, n):
# write code here
nums = [0 for i in range(n)]
for i in range(1,n):
for j in range(i):
if A[i] >= A[j]:
nums[i] += A[j]
return sum(nums)
import java.util.*;
public class MonoSum {
public int calcMonoSum(int[] A, int n) {
// write code here
if (A == null || n == 0) {
return 0;
}
return mergeSortRecursion(A, 0, n - 1);
}
/**
* 递归实现归并排序
*
* @param arr
* @param l
* @param r
* @return 返回数组小和
*/
public static int mergeSortRecursion(int[] arr, int l, int r) {
if (l == r) { // 当待排序数组长度为1时,递归开始回溯,进行merge操作
return 0;
}
int mid = (l + r) / 2;
return mergeSortRecursion(arr, l, mid) + mergeSortRecursion(arr, mid + 1, r) + merge(arr, l, mid, r);
}
/**
* 合并两个已排好序的数组s[left...mid]和s[mid+1...right]
*
* @param arr
* @param left
* @param mid
* @param right
* @return 返回合并过程中累加的数组小和
*/
public static int merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1]; // 辅助存储空间 O(n)
int index = 0;
int i = left;
int j = mid + 1;
int smallSum = 0; // 新增,用来累加数组小和
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
// 当前一个数组元素小于或等于后一个数组元素时,累加小和
// s[i] <= s[j] -> s[i] <= s[j]...s[right]
smallSum += arr[i] * (right - j + 1);
temp[index++] = arr[i++];
} else {
temp[index++] = arr[j++];
}
}
while (i <= mid) {
temp[index++] = arr[i++];
}
while (j <= right) {
temp[index++] = arr[j++];
}
for (int k = 0; k < temp.length; k++) {
arr[left++] = temp[k];
}
return smallSum;
}
} import java.util.*;
public class MonoSum {
public int calcMonoSum(int[] A, int n) {
// write code here
int count=0;
for(int i=0; i<n; i++){
int[] temp=new int[i];
for(int j=0; j<i; j++)
temp[j]=A[j];
count+=f(A[i],temp);
}
return count;
}
public int f(int a, int[] temp){
int count=0;
for(int i=0; i<temp.length; i++){
if(temp[i]<=a)
count+=temp[i];
}
return count;
}
}
给出一个用动态规划求解的方法
import java.util.*;
public class MonoSum {
public int calcMonoSum(int[] A, int n) {
// write code here
int len =A.length;
int[] dandiao = new int[len];
//初始化
for(int i=0;i<len;i++){
dandiao[i] = 0;
}
for(int i=1;i<len;i++){
for(int j=0;j<i;j++){
if(A[j]<=A[i]){
if(dandiao[i]<=dandiao[j]+A[j]){
dandiao[i]=dandiao[j]+A[j];
}else{
dandiao[i] = dandiao[i]+A[j];
}
}
}
}
System.out.println(Arrays.toString(dandiao));
int res = 0;
for(int i=0;i<dandiao.length;i++){
res = res+dandiao[i];
}
return res;
}
}
「数组单调和」,也叫「数组小和」,指的是数组中所有元素 i 的 f(i) 值之和。这里的 f(i) 函数定义为元素 i 左边(不包括其自身)小于等于它的数字之和。
举个例子,对数组 [1,3,5,2,4,6],其数组单调和(最小和)为 27,求解过程如下。
f(arr[0]) = 0 f(arr[1]) = 1 f(arr[2]) = 1 + 3 = 4 f(arr[3]) = 1 f(arr[4]) = 1 + 3 + 2 = 6 f(arr[5]) = 1 + 3 + 5 + 2 + 4 = 15 sum = 1 + 4 + 1 + 6 + 15 = 27
此处,以 smallSum(i) 表示数组前 i 个元素的数组单调和(最小和),观察其计算规律。
# [1] smallSum(0) = 0; # [1,3] smallSum(1) = 1 = smallSum(0) + 1 # [1,3,5] smallSum(2) = 5 = smallSum(1) + (1+3) # [1,3,5,2] smallSum(3) = 6 = smallSum(2) + (1) # [1,3,5,2,4] smallSum(4) = 12 = smallSum(3) + (1+3+2) # [1,3,5,2,4,6] smallSum(5) = 27 = smallSum(4) + (1+3+5+2+4)
可以发现,对数组的单调和(最小和),有如下计算公式。
smallSum(i+1) = smallSum(i) + f(arr[i+1]) f(arr[i]) 表示元素 arr[i] 左边(不包括其自身)小于等于它的数字之和
对于 f(arr[i]) 的计算,可在归并排序的 merge 阶段中计算。
举个例子,此处以数组 [1,3,5,2] 进行说明
smallSum = 0,表示数组单调和(最小和) [1] 和 [3] 进行 merge 时,发现左边元素 1 小于右边元素 3,故 smallSum += 1*1 = 1,即上文分析的 smallSum(1) = 1 [5] 和 [2] 进行 merge 时,没有发现左边元素小于右边元素,不对 smallSum 处理 [1,3] 和 [5,2] 进行 merge 时,发现左边元素 1 小于两个元素[5,2],左边元素 3 小于右边 1 个元素 [5],故 smallSum += 1*2 + 3*1 = 6,即上文分析的 smallSum(3) = 6 最后,给出使用归并排序计算数组单调和(最小和)的代码实现。
import java.util.*;
public class MonoSum {
public int smallCount = 0;
public int calcMonoSum(int[] A, int n) {
mergeSort(A,n);
return smallCount;
}
public void mergeSort(int[] A, int n){
if(A == null || n <= 1){
return;
}
sort(A,0,n-1);
}
public void sort(int[] A, int left,int right){
if(left == right){
return;
}
int mid = left + (right - left)/2;
sort(A,left,mid);
sort(A,mid+1,right);
merge(A,left,mid,right);
}
public void merge(int[] A, int left,int mid,int right){
int p1 = left;
int p2 = mid+1;
int[] tempArr = new int[right-left+1];
int index = 0;
while(p1 <= mid && p2 <= right){
if(A[p1] <= A[p2]){
//计算数组最小和
smallCount += (right-p2+1) * A[p1];
tempArr[index++] = A[p1++];
} else {
tempArr[index++] = A[p2++];
}
}
while(p1 <= mid){
tempArr[index++] = A[p1++];
}
while(p2 <= right){
tempArr[index++] = A[p2++];
}
//copy data
for(int i=0;i<tempArr.length;i++){
A[left+i] = tempArr[i];
}
}
}
import java.util.*;
public class MonoSum {
public int calcMonoSum(int[] A, int n) {
// write code here
// 思路,通过循环,将数组A分割成1~n个元素的小数组,再进行比较加和
int sum = 0;
for (int i=0; i<n; i++) {
int[] child = new int[i];
for (int j=0; j<i; j++) {
child[j] = A[j];
}
// 以上可得到分割后的每个小数组child
for(int k=0; k<child.length; k++) {
if (child[k] <= A[i]) {
sum += child[k];
}
}
}
return sum;
}
} #include <iostream>
#include <vector>
using namespace std;
// 方法一
// 参数n为以下标n-1为参照,求其前面的单调和
// int cal(const vector<int> &nums, int n) {
// if(n <= 1) return 0;
// if(n > 2e5) return 0;
// int res = 0;
// for(int i = 0; i < n - 1; ++i) {
// if(nums[i] <= nums[n - 1]) {
// res += nums[i];
// res %= (int)(1e9 + 7);
// }
// }
// return res + cal(nums, n - 1);
// }
// int main()
// {
// int n, buf;
// cin >> n;
// vector<int> nums;
// for(int i = 0; i < n; ++i) {
// cin >> buf;
// nums.push_back(buf);
// }
// cout << cal(nums, n) << endl;
// return 0;
// }
// 方法二
// int main()
// {
// int n, buf, res = 0;
// cin >> n;
// vector<int> nums;
// for(int i = 0; i < n; ++i) {
// cin >> buf;
// nums.push_back(buf);
// }
// for(int i = 1; i < n; ++i) {
// for(int j = 0; j < i; ++j) {
// if(nums[j] <= nums[i]) {
// res += nums[j];
// res %= (int)(1e9+7);
// }
// }
// }
// cout << res << endl;
// return 0;
// }
// 方法三
void merge(vector<int> &nums, int start, int mid, int end, int &count) {
vector<int> res;
int left_index = start, right_index = mid + 1;
while(left_index <= mid && right_index <= end) {
if(nums[left_index] <= nums[right_index]) {
count += (end - right_index + 1) * nums[left_index];
count %= (int)(1e9+7);
res.push_back(nums[left_index++]);
} else {
res.push_back(nums[right_index++]);
}
}
for(int i = left_index; i <= mid; ++i) {
res.push_back(nums[i]);
}
for(int i = right_index; i <= end; ++i) {
res.push_back(nums[i]);
}
for(int i = start; i <= end; ++i) {
nums[i] = res[i - start];
}
}
void mergeSort(vector<int> &nums, int start, int end, int &res) {
int mid = start + (end - start) / 2;
if(mid > start) {
mergeSort(nums, start, mid, res);
}
if(end > mid + 1) {
mergeSort(nums, mid + 1, end, res);
}
merge(nums, start, mid, end, res);
}
int main()
{
int n, buf, res = 0;
cin >> n;
vector<int> nums;
for(int i = 0; i < n; ++i) {
cin >> buf;
nums.push_back(buf);
}
if(n < 2 || n > 2e5) res = 0;
else mergeSort(nums, 0, n - 1, res);
cout << res << endl;
return 0;
}