给一序列,求和最大的子序列,如:
2
5 6 -1 5 4 -7
Case 1:
14 1 4
7 0 6 -1 1 -6 7 -5
Case 2:
7 1 6
2为序列数目。序列的一个数(5和7)是序列的元素个数。
很正常的想法,是定义一个数组接受数据,后对这个序列的每个子序列求和,找出最大的。当然其中还要运用一些技巧提高效率。下面是我写的程序:
#include <iostream>
using namespace std;
int main()
{
int pre, start, max, end;
int numOfInput, N;
int* input;
cin >> numOfInput;
for(int i = 0; i< numOfInput; i++)
{
cin >> N;
input = new int[N];
for(int j = 0; j< N;j++)cin >> input[j];
max = 0;
start = end = 1;
for(int n = 0; n< N; n++)
{
pre = input[n];
for(int m = n + 1; m< N; m++)
{
if(max < pre + input[m])
{
pre += input[m];
max = pre;
start = n + 1;
end = m + 1;
}
else pre += input[m];
}
}
cout << "Case " << i + 1 << ":\n";
cout << max << " " << start << " " << end;
if(i != numOfInput - 1)cout << endl << endl;
else cout << endl;
}
}
必然地:Time Limit Exceeded
程序最费时间的,是进行了n * (n – 1)/2比较。下面是网上流传的简单算法:
#include <iostream>
using namespace std;
int main()
{
int T,N,num,startP,endP;
cin >> T;
for(int k = 0; k< T; k++)
{
cin >> N;
int max = -1001,sum = 0,temp = 1;
for(int i = 0; i< N; i++)
{
cin >> num;
sum += num;
if(sum> max)
{
max = sum;
startP = temp;
endP = i+1;
}
if(sum< 0)
{
sum = 0;
temp = i+2;
}
}
cout<< "Case " << k+1 << ":"<< endl << max << " " << startP << " " << endP << endl;
if(k!=T-1) cout << endl;
}
return 0;
}
此算法是边输入边处理。理解此算法,先看一种输入序列特殊情况:最大子序列的起点是1,那么算法中二层循环的条件判断(sum> max)始终成立,后面的处理与我类似:sum为刚输入数据之前的和,如果sum与刚输入的数据的和大于max,那么最大值就需要变大了,否则max不变,sum更新。
对于一般情况,这个算法比我更看清楚本质:
什么情况下satrtP不等于1呢?不等于1,说明新的起点之前的所有数据之和是一个负数,因为如果把它们纳进新的子序列只会把max拉低。比如下面的序列:
a1,a2,a3,a4,a5,a6
如果最大子序列起点和终点是4,6,这说明a1 + a2 +a3< 0,否则a1加到a6是会把max的值提升的。
高效算法即是看清此点,当发现a1 + a2 +a3< 0,startP = 4,并以4作为新的“1”,前面的a1,a2,a3即变成无用的数据。也就是说,任何序列都可以转换成最大子序列起点为1的情况进行处理