最大子段和
最大子段和
Description
给定由n(1<n<100)个整数(包含负整数)组成的序列a1,a2,...,an,要在这n个数中选取相邻的一段ai,ai+1,...aj(1≤i≤j≤n),使其和最大,输出该序列子段和的最大值。
Input
输入有多组测试用例
每组测试用例由2行组成,第一行一个整数n,接下来第二行有n个整数
若整数n为0,表示输入结束
Output
输出该序列子段和的最大值
Sample Input
8
1 -3 7 8 -4 12 -10 6
0
Sample Output
23
问题分析及解答
/*前置代码*/
#include<iostream>
#include<cstdio> using namespace std; const int maxn = 1005; int a[maxn], _max[maxn]; /*在此加入工作代码 eg: work() */ /*后置代码*/ int main() { //freopen("in.txt","r",stdin); while(cin>>n, n) { for(int i = 1; i <= n; i ++) { cin>>a[i];//在整个过程为方便操作,我们数组的下表从 “1” 开始。 } work(); //or work(1,n); cout<<m<<endl; } return 0; }
我们很容易能够想到暴力的方法,利用三个循环嵌套。(O(n^3))
//1.暴力法 void work() { for(int i = 1; i <= n; i++) { for(int j = i; j <= n; j++) { int curm = 0;//保存当前的和。 for(int k = i; k <= j; k++) { curm += a[k]; if(curm > m) { m = curm; } } } } }
观察发现,暴力法存在大量的重复计算,可以进行一定的优化。(O(n^2))
//2.优化代码 void work() { for(int i = 1; i <= n; i++) { int curm = 0; for(int j = i; j <= n; j++) { curm += a[j]; if(curm > m) { m = curm; } } } }
优化的效率任不是很高,我们可以考虑分治法。(O(nlog(n)))
将a[1n]分成a[1n/2]和a[n/2+1n]两部分,会产生一下三种情况
(1)a[1n]的最大子段和与a[1n/2]的最大子段和相同
(2)a[1n]的最大子段和与a[n/2n]的最大子段和相同
(3)a[1n]的最大子段和为ai++aj,1<=i<=n/2,n/2+1<=j<=n
我们的目的是求出上述三种情况中最大的一种。
//3.分治法
int work(int l, int r)//l: left r: right { if(l == r) m = a[l];// > 0 ? a[l] : 0; else { int c = (l + r) / 2;//c: center int lm = work(l,c);//lm: left sum int rm = work(c+1,r);//rm: right sum int clm = 0;//center's left sum int tlm = 0;//temp left sum for(int i = c; i >= l; i--) { tlm += a[i]; if(tlm > clm) clm = tlm; } int crm = 0;//center's right sum int trm = 0;//temp right sum for(int i = c + 1; i <= r; i++) { trm += a[i]; if(trm > crm) crm = trm; } int cm = clm + crm;//center sum m = cm < lm ? ( rm < lm ? lm : rm) : (cm < rm ? rm : cm); } return m; }
尽管上述代码已经比暴力好了很多,但是由于递归调用过程存储空间分配所带来的时间浪费有时也是不可小觑的。
我们介绍一种更高效的算法——动态规划(O(n)),过程如下:
记 _max[i] 为a[0…i]中,包含a[i]的最大子段和,则有
(1) _max[i] = s[i-1]+a[i], s[i-1] > 0,
(2) _max[i] = a[i], s[i-1] <= 0;
我们的目的是求max(_max)
//4.动态规划 void work() { m =a[1]; _max[1] = a[1]; for(int i = 2; i <=n; i++) { _max[i] = _max[i-1] > 0 ? _max[i - 1] + a[i] : a[i]; if(m < _max[i]) m = _max[i]; } }
也可以直接将a[0:j]的最大子段和(注意与上面包含a[j]的最大子段和的数组_max[]的区别)保存在一个数组b[]中,这样就不用求max(_max)了,完整代码如下:
#include<iostream> #include<cstdio> using namespace std; const int maxn = 1005; int a[maxn], _max[maxn]; int b[maxn];//b[j]保存a[i:j]的最大子段和。 int n, m; void work() { m =a[1]; _max[1] = a[1]; b[1] = a[1]; for(int i = 2; i <=n; i++) { _max[i] = _max[i-1] > 0 ? _max[i - 1] + a[i] : a[i]; if(m < _max[i]) { m = _max[i]; b[i] = m; } else b[i] = b[i - 1]; } } int main() { //freopen("in.txt","r",stdin); while(cin>>n, n) { for(int i = 1; i <= n; i ++) { cin>>a[i]; } work(); cout<<b[n]<<endl; } return 0; }
这个题目仅仅求出了最大字段和,感兴趣的朋友可以自己试着求出最大子段和的区间。
The quieter you become, the more you are able to hear.