最大子序列和、最小子序列和、最小正子序列和、最大子序列乘积
一、先说最大子序列和问题,四种解法,时间复杂度依次递减:
1、O(N^3)
1 int MaxSubsequenceSum (const int A[], int N) 2 { 3 int ThisSum, MaxSum = 0; 4 5 for(int i = 0; i != N; ++i) 6 for(int j = i; j != N; ++j){ 7 ThisSum = 0; 8 for(int k = i; k != j; ++k) 9 ThisSum += A[k]; 10 if(MaxSum < ThisSum) 11 MaxSum = ThisSum; 12 } 13 return MaxSum; 14 }
2、O(N^2)
1 int MaxSubsequenceSum (const int A[], int N) 2 { 3 int ThisSum, MaxSum = 0; 4 5 for(int i = 0; i != N; ++i){ 6 ThisSum = 0; 7 for(int j = i; j != N; ++j){ 8 ThisSum += A[j]; 9 if(MaxSum < ThisSum) 10 MaxSum = ThisSum; 11 } 12 } 13 return MaxSum; 14 }
3、O(N*logN)--分治递归--假设N是偶数
1 int Max3(int a, int b, int c) 2 { 3 int max; 4 max = a > b ? a : b; 5 max = max > c ? max : c; 6 7 return max; 8 } 9 10 static int MaxSubSum (const int A[], int Left, int Right)//Left左边界(0) Right右边界(N-1) 11 { 12 int MaxLeftSum, MaxRightSum; 13 int MaxLeftBorderSum, MaxRightBorderSum; 14 int LeftBorderSum, RightBorderSum; 15 int Mid; 16 17 if(Left == Right) 18 if(A[Left] > 0) 19 return A[Left]; 20 else 21 return 0; 22 23 Mid = (Left + Right) / 2; 24 MaxLeftSum = MaxSubSum(A, Left, Mid); //左侧递归 25 MaxRightSum = MaxSubSum(A, Mid+1, Right); //右侧递归 26 27 MaxLeftBorderSum = 0; LeftBorderSum = 0; //相比于第二种算法,去掉一个循环,每次分一半来求解最大值 28 for(int i = Mid; i != Left-1; --i){ 29 LeftBorderSum += A[i]; 30 if(LeftBorderSum > MaxLeftBorderSum) 31 MaxLeftBorderSum = LeftBorderSum; 32 } 33 34 MaxRightBorderSum = 0; RightBorderSum = 0; 35 for(int i = Mid+1; i != Right+1; ++i){ 36 RightBorderSum += A[i]; 37 if(RightBorderSum > MaxRightBorderSum) 38 MaxRightBorderSum = RightBorderSum; 39 } 40 41 return Max3(RightBorderSum, MaxRightSum, // 比较左侧最大值,右侧最大值和左右相加最大值 42 MaxLeftBorderSum+MaxRightBorderSum); 43 }
4、O(N)
1 int MaxSubsequenceSum (const int A[], int N) 2 { 3 int ThisSum, MaxSum; 4 ThisSum = MaxSum = 0; 5 6 for(int i = 0; i != N; ++i){ 7 ThisSum += A[i]; 8 if(MaxSum < ThisSum) 9 MaxSum = ThisSum; 10 else if(ThisSum < 0) 11 ThisSum = 0; 12 } 13 return MaxSum; 14 }
该算法附带的一个优点是,它只对数据进行一次扫描,一旦A[i]被读入并处理,它就不再需要被记忆。不仅如此,在任何时刻,算法都能对它已经读入的数据给出子序列问题的正确答案。具有这种特性的算法叫联机(online)算法。仅需要常量空间并以线性时间运行的联机算法几乎是完美的算法。
二、最小子序列和O(N)
只是改变判断条件符号的方向。
1 int MaxSubsequenceSum (const int A[], int N) 2 { 3 int ThisSum, MaxSum; 4 ThisSum = MaxSum = 0; 5 6 for(int i = 0; i != N; ++i){ 7 ThisSum += A[i]; 8 if(MaxSum > ThisSum) 9 MaxSum = ThisSum; 10 else if(ThisSum > 0) 11 ThisSum = 0; 12 } 13 return MaxSum; 14 }
三、最小正子序列和
感谢@windrang指出我的错误。
1、O(N^2)
for (int i = 0; i != N; ++i) { ThisSum = 0; for (int j = i; j != N; ++j) { ThisSum += A[j]; if (MinSum > ThisSum && ThisSum > 0) MinSum = ThisSum; } }
2、O(NlogN)
#include <iostream> #include <algorithm> #include <string.h> struct Item { int value; int index; }; bool cmp(Item& a, Item& b) { if (a.value == b.value) { a.index = b.index = a.index < b.index ? a.index : b.index; } return a.value < b.value; } int MinSubsequenceSum (const int A[], int N) { int ThisSum = 0, MinSum = 0; Item* B = new item[N]; memset(B, 0, sizeof(Item) * N); for (int i = 0; i < N; ++i) { B[i].index = i; ThisSum += A[i]; B[i].value = ThisSum; } std::sort(B, B+N, cmp); MinSum = B[0].value > 0 ? B[0].value : 2<<29; for (int i = 1; i < N; ++i) { if ((B[i-1].index < B[i].index) && (B[i].value - B[i-1].value > 0) && (B[i].value - B[i-1].value < MinSum)) { MinSum = B[i].value - B[i-1].value; } } delete[] B; return MinSum; } int main () { int A[8] = {4,-1,6,-2,-1,3,6,-2}; // 4, 0, 4, 1, -1 std::cout << MinSubsequenceSum(A, 8); return 0; }
这里是是根据绿色夹克衫老爷的答案来写的。其中需要注意的是排序,在判断函数中,需要将相同元素值的序列合并为较小的序列。否则的话,序列(4, 0, 4, 1, -1),得出的答案为4。
四、最大子序列乘积
DP
思路: 以元素i结尾序列提供的最大正数记做 pos, 最小负数记做 nag a[n] 大于零时: pos[n] = max{pos[n-1] * a[n], a[n]} max_value = max{max_value, pos[n]} 若n-1位置存在最小负数, 更新 nag[n] = nag[n-1] * a[n] a[n] 小于零时: pos[n] = max{nag[n-1] * a[n], 0.0} max_value = max{max_value, pos[n]} 更新 nag[n] = min{pos[n-1] * a[n], a[n]} a[n] 等于零时: 清空 nag[n] 与 pos[n]
代码,
#include <iostream> inline double max(const double& a, const double& b)
{ return (a > b) ? a : b; } double MaxSubsequenceProduct(double a[], int len) { double Max = 0.0, pos = 0.0, old = 0.0, nag = 1.0; for (int i = 0; i < len; ++i) { if (a[i] > 1e-6) { pos = max(old * a[i], a[i]); Max = max(Max, pos); if (nag < -1e-6) { nag *= a[i]; } } else if (a[i] < -1e-6) { pos = max(0.0, nag * a[i]); Max = max(Max, pos); nag = (old * a[i] > a[i]) ? a[i] : old * a[i]; } else { nag = 1.0; pos = 0.0; } old = pos; } return Max; } int main() { double a[7] = { -2.5, 4, 0, 3, 0.5, 8, -1 }; std::cout << MaxSubsequenceProduct(a, 7); std::cin >> a[2]; return 0; }
出处来自木叶漂舟。