问题描述:
给定K个整数的序列{ N1N2 ... NK },其任意连续子序列可表示为{ NiNi+1 ...Nj },其中 1<= i <= j <= K。最大连续子序列是所有连续子序列中元素和最大的一个, 例如给定序列{ -211 -4 13 -5 -2 },其最大连续子序列
输入:
一行测试数据。
输出:
最大连续子序列的和。
样例:
输入:
-2 11 -4 13 -5 -2
输出:
20
最粗暴的方法,枚举
(1)按照子序列的长度来枚举,从长度为1到长度为n依次枚举,比较大小,最后得出最大的子序列。
#include<iostream>
#include<string>
#include<sstream>
using namespace std;
const int MAX=100000000;
int a[MAX];
int main()
{
string s;
getline(cin,s); //将输入数据(包括空格)保存在字符串s中
istringstream ss(s);
int e,n=0,i;
while(ss>>e) //将用空格隔开的数据输入到数组中
{
a[n++]=e;
}
int ans=0;
for(i=1;i<n;i++) //设置子序列长度
{
for(int j=0;j<n-i;j++) //设置子序列开始的位置
{
int sum=0;
for(int k=j;k<j+i;k++)
{
sum+=a[k];
}
ans=sum>ans?sum:ans;
}
}
cout<<ans<<endl;
return 0;
}
(2)可以按照开始位置来枚举,从开始位置为1(枚举长度从1到n),到开始位置为2....直到开始位置为n.
#include<iostream>
#include<string>
#include<sstream>
using namespace std;
#define MAX 100000000
int a[MAX];
int main()
{
string s;
getline(cin,s);
istringstream ss(s);
int e,n=0,i;
while(ss>>e)a[n++]=e;
int ans=0;
for(i=0;i<n;i++) //设置开始位置i
{
for(int j=1;j<n;j++) //设置长度j
{
int sum=0;
for(int k=i;k<j+i;k++)sum+=a[k]; //从开始位置i到长度为j为止
ans=ans>sum?ans:sum;
}
}
cout<<ans<<endl;
return 0;
}
我们可以看出中间有大量重复的过程,因为可以优化为N^2。
#include<iostream>
#include<string>
#include<sstream>
using namespace std;
#define MAX 100000000
int a[MAX];
int main()
{
string s;
getline(cin,s);
istringstream ss(s);
int e,n=0,i;
while(ss>>e)a[n++]=e;
int ans=0;
for(i=0;i<n;i++) //设置开始位置
{
int sum=0; //这个sum用来记录长度为j-1的和。
for(int j=i;j<n;j++) //设置长度
{
sum+=a[j];
ans=ans>sum?ans:sum;
}
}
cout<<ans<<endl;
return 0;
}
采用二分法,可以达到nlogn
三种情况。情况一:左边的大;情况二:右边的大;情况三:中间连着左右两边部分序列构成的序列大。
#include<iostream>
using namespace std;
const int MAX=10000000;
int a[MAX];
int ans=0;
int digui(int start,int end);
int main()
{
int n;
cin>>n;
int i;
for(i=0;i<n;i++)cin>>a[i];
int ans=digui(0,n-1);
cout<<ans<<endl;
return 0;
}
int digui(int start,int end)
{
if(start==end)return a[start];
//开始二分
int mid=(start+end)>>1; // >>后面是右移多少位
int left=digui(start,mid);
int right=digui(mid+1,end);
//如果中点加上左右两边为最大
int sum=0,left_max=a[mid],right_max=a[mid+1]; //left_max和right_max的取值,左右两边都至少取一个,做到横跨左右,不能取0,如果序列都为负数,就不对了
for(int i=mid;i>=start;i--)
{
sum+=a[i];
if(sum>left_max)left_max=sum;
}
sum=0;
for(int i=mid+1;i<=end;i++)
{
sum+=a[i];
if(sum>right_max)right_max=sum;
}
//比较三种情况
int lr_sum=right_max+left_max;
if(left>lr_sum) lr_sum=left; //很好的做到了三个数的比较
if(right>lr_sum)lr_sum=right;
return lr_sum;
}
采用前缀和,达到n
前缀和是从序列第一个到最后一个过程中记录下最大的和值,减去最小的和值,中间这一段就是最大子序列。
#include<iostream>
#include<iostream>
using namespace std;
int main()
{
int n;
cin>>n;
int i;
int array[n];
for(i=0;i<n;i++)cin>>array[i];
int ans=array[0],lmin=array[0]<0?array[0]:0; //如果第一位是正,那lmin=0,如果为负,则是这个数
for(i=1;i<n;i++)
{
array[i]+=array[i-1]; //直接用原来的数组来计算前缀和,并在计算过程中更新ans
//让现在的lmin一直在最大的和的左边!!!!
if(array[i]-lmin>ans)ans=array[i]-lmin; //如果这个和减去记录的最小的和 大于ans ,更新ans
if(array[i]<lmin)lmin=array[i]; //如果该和小于记录的最小的和,更新lmin
}
cout<<ans<<endl;
return 0;
}
这个还可以优化,不用数组存储,用变量存储。
#include<iostream>
using namespace std;
int main()
{
int n;
cin>>n;
int num;
cin>>num;
int ans=num,sum=num,lmin=0; //ans要赋值为第一个数,第一个数要被检验,所以lmin可以直接复值为0。
for(int i=1;i<n;i++)
{
if(sum<lmin)lmin=sum;
cin>>num;
sum+=num;
if(sum-lmin>ans)ans=sum-lmin;
}
cout<<ans<<endl;
return 0;
}
用dp,时间复杂度为o(n)
用dp的思想,就是从n往回看,0为开头,如果最大子序列的末尾位置为1,那么有1种选择,如果为2,有两种,如果为3,有3种。。。末尾位置是否选择取决于前面,即a[i]是否要加入sum[i-1]之前的和中,如果为负的,就不加入,如果为正,就加入进去。
如果前面的和为负的,把这个数加进去比自己本身要小,肯定是不对的。
#include<iostream>
using namespace std;
int main()
{
int n;
cin>>n;
int i;
int array[n];
for(i=0;i<n;i++)cin>>array[i];
int sum=0,ans=0;
for(i=0;i<n;i++)
{
if(sum<0)sum=0;
sum+=array[i];
ans=ans>sum?ans:sum;
}
cout<<ans<<endl;
return 0;
}
以上就是我对最大连续子序列各种解法的总结。