编程之美2.14——求数组的子数组之和的最大值

问题:

1. 一个由N个整数元素的一维数组,求其所有子数组中元素和的最大值。

2. 如果数组首尾相邻,也就是允许子数组A[i],...,A[n-1],A[0],...,A[j]存在,求其所有子数组总元素和的最大值。


1. 解法:

我们使用动态规划的思想可以在O(n)的时间内计算出子数组之和最大值。

动态规划问题往往非常灵活,所以在做题的时候不知道如何编写,其实它有很清晰的分析方法,按照这个方法就可以较容易的解决动态规划的问题。具体参考IOI2000 张辰的论文《动态规划的特点及其应用》

阶段:在所有以元素k结尾的子数组中,选出元素和最大的子数组,k=1,2...n。
状态以元素k结尾的子数组中只有一个元素和最大的子数组。
决策:决定元素k结尾的和最大的子数组有两种获取的途径,一个是以元素k-1结尾的和最大的子数组,另一个是只有元素k这一个元素。

之所以选择这样的阶段和状态,因为这种选择方式具有无后效性,即阶段k+1(以元素k+1结尾的子数组)只会与阶段k(以元素k结尾的子数组)发生关联,而与其它阶段无关。在得到以每个元素结尾的和最大的子数组之后,只要取其中最大值就是所有子数组中最大的子数组。

#include <iostream>
#include <algorithm>
using namespace std;

#define MAXN 1003
int A[MAXN];

// 动态规划思想,时间复杂度O(n)
int Tail[MAXN];

int main()
{
	int n, i, j, k;
	cin >> n;
	for (i=1; i<=n; i++)
		cin >> A[i];
	// 计算以k结尾的子数组之和的最大值,即子数组包含第k个数
	Tail[1] = A[1];
	for (k=2; k<=n; k++)	// k个阶段
		Tail[k] = max(A[k],Tail[k-1]+A[k]);	// 有两种决策
	// 因为和最大的子数组肯定以某个数结尾,所以取这n个子数组的最大值
	// 就是和最大的子数组
	int All = Tail[1];
	for (i=2; i<=n; i++)
		All = max(All, Tail[i]);
	cout << All;
}
虽然这种标准的动态规划方法时间复杂度已经最优了,但它仍要占用O(n)的空间,对于一般的动态规划问题占用较多的空间是不可避免的,但这个问题较简单,仍可以继续优化。我们把All取最大值的操作放入Tail的计算循环中,如下:

Tail[1] = A[1];
All = Tail[1];
for (k=2; k<=n; k++)// k个阶段
{
Tail[k] = max(A[k],Tail[k-1]+A[k]);// 只有两个状态
All = max(All, Tail[k]);
}

由于循环体中只关心当前的Tail[k]和上一个Tail[k-1],可以省去之前所计算出的Tail[1],Tail[2]...Tail[k-2]的数据,如下:

Tail = A[1];
All = Tail;
for (k=2; k<=n; k++)// k个阶段
{
Tail = max(A[k],Tail+A[k]);// 只有两个状态
All = max(All, Tail);
}

最后优化后的代码就是书中最后给出的结果。


2. 解法:

把问题分为两种情况:

(1)最大和子数组没有跨过A[n]到A[1](如问题1)

(2)最大和子数组跨过A[n]到A[1]

对于情况(2),这样的最大和子数组包含两个部分:以A[1]开始的最大和子数组,以及以A[n]结尾的最大和子数组,并且这两个子数组不允许重叠,那么将这两个子数组拼合起来就是情况(2)的解。

#include <iostream>
#include <algorithm>
using namespace std;

#define MAXN 1003
int A[MAXN];

int main()
{
	int n, i, j, k;
	cin >> n;
	for (i=1; i<=n; i++)
		cin >> A[i];
	// 和最大的子数组没有跨过A[n]和A[1]
	int Tail = A[1];
	int All = Tail;
	for (k=2; k<=n; k++)	// k个阶段
	{
		Tail = max(A[k],Tail+A[k]);	// 有两种决策
		All = max(All, Tail);
	}
	// 和最大的子数组跨过了A[n]和A[1]
	int Start = A[1];
	for (i=2; i<=n && Start+A[i]>Start; i++)
			Start += A[i];
	Tail = A[n];
	for (j=n-1; j>=1 && Tail+A[j]>Tail; j--)
			Tail += A[j];
	if (i<j && Start+Tail > All)
		All = Start+Tail;
	cout << All;
}

刚发现上面的代码有bug,拿测试数据-2, -1, 8, -10, 5, -1 ,3进行测试就会发现,上面程序给出的结果是8,但实际上的结果应该是12(即5, -1, 3, -2, -1, 8),原因在于计算以A[1]开始的最大和子数组,以及以A[n]结尾的最大和子数组的算法没有达到效果。正确代码如下(虽然题目简单但还是要细心):

#include <iostream>
#include <algorithm>
using namespace std;

#define MAXN 1003
int A[MAXN];

int main()
{
	int n, i, j, k;
	cin >> n;
	for (i=1; i<=n; i++)
		cin >> A[i];
	// 和最大的子数组没有跨过A[n]和A[1]
	int Tail = A[1];
	int All = Tail;
	for (k=2; k<=n; k++)	// k个阶段
	{
		Tail = max(A[k],Tail+A[k]);	// 有两种决策
		All = max(All, Tail);
	}
	// 和最大的子数组跨过了A[n]和A[1]
	int Sum = A[1];
	int Start = Sum, sind = 1;
	for (i=2; i<=n; i++)
	{
		Sum += A[i];
		if (Sum > Start) {Start = Sum;sind = i;}
	}
	Tail = Sum = A[n];
	int tind = n;
	for (j=n-1; j>=1; j--)
	{
		Sum += A[j];
		if (Sum > Tail) {Tail = Sum;tind = j;}
	}
	if (sind<tind && Start+Tail > All)
		All = Start+Tail;
	cout << All;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值