最大连续和问题


最大连续和问题的提法:

      给出一个长度为n的序列A1,A2,...,An,求最大的连续和(Ai+Ai+1...+Aj)。

1、首先最容易想到的思路是进行枚举,即枚举所有的i和j,累加Ai到Aj,然后更新最大值。

实现如下:(时间复杂度为O(n^3))

 

#include <iostream>
using namespace std;

int a[100], n;

int main()
{
	cin >> n;
	for(int i = 1; i <= n; i ++) scanf("%d",&a[i]);	//输入数据
	
	int max = -0x7fffffff;
	int sum = 0;
	for(int i = 1; i <= n; i ++)//O(n^2)
		for(int j = i; j <= n; j ++){
			sum = 0;
			for(int k = i; k <= j; k ++) sum += a[k];
			if(sum > max) max = sum;
		}
	cout << max <<endl;
	
	return 0;
}

2、对于上面的直接枚举方法,还可以进行预处理,以改进时间复杂度为O(n^2)

预处理的方法是累加前i个元素的和,并存放起来S[i],而S[i]的计算是可以递推求解的。预处理时间花费为O(n)。

然后枚举的时候,A[i..j]这一子序列的和就可以在O(1)时间内求出来了。A[i..j] = S[j] - S[i-1].

实现如下:(时间复杂度为O(n^2))

#include <iostream>
using namespace std;

int a[100], s[100], n;

int main()
{
	cin >> n;
	for(int i = 1; i <= n; i ++) scanf("%d",&a[i]);	//输入数据

	//预处理,求累加和
	s[0] = 0;
	for(int i = 1; i <= n; i ++) s[i] = s[i-1] + a[i];

	//枚举i,j
	int max = -0x7fffffff;
	int sum = 0;
	for(int i = 1; i <= n; i ++)//O(n^2)
		for(int j = i; j <= n; j ++){
			sum = s[j] - s[i-1];
			if(sum > max) max = sum;
		}
	cout << max <<endl;
	
	return 0;
}

3、根据《算法竞赛入门经典》一书上的说法,还可以得到O(n)的解法。

      即sum = s[j] - s[i-1],而i<j,故对于确定的j,要使sum最大,让s[i-1]最小即可,可以维护一个s的最小值,从而直接使用。

实现如下:(时间复杂度为O(n))

#include <iostream>
using namespace std;

int a[100], s[100], n;

int main()
{
	cin >> n;
	for(int i = 1; i <= n; i ++) scanf("%d",&a[i]);	//输入数据

	//预处理,求累加和
	s[0] = 0;
	for(int i = 1; i <= n; i ++) s[i] = s[i-1] + a[i];

	//枚举i,j
	int max = -0x7fffffff;
	int min = s[0];
	for(int i = 1; i <= n; i ++){		
		if(s[i] - min > max) max = s[i] - min;
		if(s[i] < min) min = s[i];//min保存了s[1]~s[i-1]中的最小值
	}
	cout << max <<endl;
	
	return 0;
}


4、分治法

      首先,分治法的三个步骤:划分问题、递归求解、合并问题。

      划分问题:  将序列二分

      递归求解:分别求解左右两边子序列的最大连续和

      合并问题:最大连续和A[i...j]可能就在左子序列(其解为LL),也可能在右子序列(其解为RR),也可能跨越了划分点(即中点,左右两边各有一部分),因此,如果是第三种情况,则以划分点为终点向左边扫描以求出最大连续和L,类似的,以划分点为起始点向右扫描,以求出最大连续和R,则问题的解为max{LL,RR,L+R}

      实现如下:(时间复杂度为O(nlogn),因为合并问题花费O(n),于是:T(n) = 2T(n/2) + n,其解为O(nlogn))

#include <iostream>
using namespace std;

int a[100], s[100], n;

int max(int a, int b){return a > b ? a : b;}

int maxSubSum(int * a, int x, int y) //区间为[x,y)
{
	//边界情况->只有一个元素
	if(y-x == 1) return a[x];

	//否则进行二分
	int m = x + (y-x)/2;//学的《算法竞赛入门经典》,这样可以保证中点更靠近x	
	int LL = maxSubSum(a,x,m);//左子问题的解
	int RR = maxSubSum(a,m,y);//右子问题的解
	int sum = 0;//以m为终点向左右半序列扫描求最大连续和
	int L = -0x7fffffff;
	for(int i = m; i >= x; i --){
		sum += a[i];
		if(sum > L) L = sum;
	}
	int R = -0x7fffffff;
	sum = 0;
	for(int i = m; i < y; i ++){
		sum += a[i];
		if(sum > R) R = sum;		
	}

	//合并解
	int res = max(LL,RR);
	res = max(res,L+R-a[m]);//左右扫描的时候a[m]各算了一次
	return res;
}

int main()
{
	cin >> n;
	for(int i = 1; i <= n; i ++) scanf("%d",&a[i]);	//输入数据
	
	cout << maxSubSum(a,1,n+1) <<endl;//注意参数的范围是开区间[1,n+1)
	
	return 0;
}


5、总结

     上面讨论了最大连续和问题的4种解法和实现的代码,另外,测试的时候用的测试用例如下:

           输入:

            9
            1 2 -2 3 4 5 -6 7 -10

          输出:

            14

     从可以看到,一个问题的不同解法,其时间复杂度相差甚大。从O(n^3)到O(n^2)的改进,是通过算累加和的预处理实现的。而从O(n^2)到O(n)的改进则是通过进一步的递推(维护当前所见到的最小的s[i-1])实现的。而分治法能达到O(nlogn)的时间复杂度,是因为其复杂度函数为T(n) = 2T(n/2) + n。上面的改善过程就是不断减少冗余计算的过程,应该多注意体会递推的思想、分治法的思想和步骤、预处理的方法。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值