最大连续子序列从N^3到二分到前缀和到DP

问题描述:

  给定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;
}

以上就是我对最大连续子序列各种解法的总结。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值