动态规划2-最大子段和

给出一个整数数组a(正负数都有),如何找出一个连续子数组(可以一个都不取,那么结果为0),使得其中的和最大?

例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。

看见这个问题你的第一反应是用什么算法?

(1) 枚举?对,枚举是万能的!枚举什么?子数组的位置!好枚举一个开头位置i,一个结尾位置j>=i,再求a[i..j]之间所有数的和,找出最大的就可以啦。好的,时间复杂度?

(1.1)枚举i,O(n)
(1.2)枚举j,O(n)
(1.3)求和a[i..j],O(n)

大概是这样一个计算方法:

for(int i = 1; i <= n; i++)
{
    for(int j = i; j <= n; j++)
    {
        int sum = 0;
        for(int k = i; k <= j; k++)
            sum += a[k];
            
        max = Max(max, sum);
    }
}

所以是O(n^3), 复杂度太高?降低一下试试看?

(2) 仍然是枚举! 能不能在枚举的同时计算和?
(2.1)枚举i,O(n)
(2. 2)枚举j,O(n) ,这里我们发现a[i..j]的和不是a[i..j – 1]的和加上a[j]么?所以我们在这里当j增加1的时候把a[j]加到之前的结果上不就可以了么?对!所以我们毫不费力地降低了复杂度,得到了一个新地时间复杂度为O(n^2)的更快的算法。

大概是这样一段代码:

for(int i = 1; i <= n; i++)
{
    int sum = 0;
    
    for(int j = i; j <= n; j++)
    {
        sum += a[j];
        max = Max(max, sum);
    }
}

是不是到极限了?远远不止!

(3)分治一下?

我们从中间切开数组,原数组的最大子段和要么是两个子数组的最大子段和(分), 要么是跨越中心分界点的最大子段和(合)。 那么跨越中心分界点的最大子段合怎么计算呢?仍然是枚举! 从中心点往左边找到走到哪里可以得到最大的合,再从中心点往右边检查走到哪里可以得到最大的子段合,加起来就可以了。可见原来问题之所以难,是因为我们不知道子数组从哪里开始,哪里结束,没有“着力点”,有了中心位置这个“着力点”,我们可以很轻松地通过循环线性时间找到最大子段和。

于是算法变成了

(3.1)拆分子数组分别求长度近乎一半的数组的最大子段和sum1, sum2

时间复杂度 2* T(n / 2)

(3.2)从中心点往两边分别分别找到最大的和,找到跨越中心分界点的最大子段和sum3 时间复杂度 O(n)

那么总体时间复杂度是T(n) = 2 * T(n / 2) + O(n) = O(nlogn), 又优化了一大步,不是吗?

还能优化吗?再想想,别放弃!

我们在解法(3)里需要一个“着力点”达到O(n)的子问题时间复杂度,又在解法(2)里轻易地用之前的和加上一个新的元素得到现在的和,那么“之前的和”有那么重要么?如果之前的和是负数呢?显然没用了吧?我们要一段负数的和,还不如从当前元素重新开始了吧?

再想想,如果我要选择a[j],那么“之前的和”一定是最大的并且是正的。不然要么我把“之前的和”换成更优,要么我直接从a[j]开始,不是更好么?

动态规划大显身手。我们记录dp[i]表示以a[i]结尾的全部子段中最大的和。我们看一下刚才想到的,我取不取a[i – 1],如果取a[i – 1]则一定是取以a[i – 1]结尾的子段和中最大的一个,所以是dp[i – 1]。 那如果不取dp[i – 1]呢?那么我就只取a[i]孤零零一个好了。注意dp[i]的定义要么一定取a[i]。 那么我要么取a[i – 1]要么不取a[i -1]。 那么那种情况对dp[i]有利? 显然取最大的嘛。所以我们有dp[i] = max(dp[i – 1] + a[i], a[i]) 其实它和dp[i] = max(dp[i – 1] , 0) + a[i]是一样的,意思是说之前能取到的最大和是正的我就要,否则我就不要!初值是什么?初值是dp[1] = a[1],因为前面没的选了。

那么结果是什么?我们要取的最大子段和必然以某个a[i]结尾吧?那么结果就是max(dp[i])了。

这样,我们的时间复杂度是O(n),空间复杂度也是O(n)——因为要记录dp这个数组。

算法达到最优了吗? 好像是!还可以优化!我们注意到dp[i] = max(dp[i - 1], 0) + a[i], 看它只和dp[i – 1]有关,我们为什么要把它全记录下来呢?为了求所有dp[i]的最大值?不,最大值我们也可以求一个比较一个嘛。

我们定义endmax表示以当前元素结尾的最大子段和,当加入a[i]时,我们有endmax’ = max(endmax, 0) + a[i], 然后再顺便记录一下最大值就好了。

伪代码如下;(数组下标从1开始)

endmax = answer = a[1]
for i = 2 to n do
    endmax = max(endmax, 0) + a[i]
    answer = max(answer, endmax)
endfor

时间复杂度?O(n)!空间复杂度?O(1)! 简单吧?我们不仅优化了时间复杂度和空间复杂度,还使代码变得简单明了,更不容易出错。

老生常谈的问题来了。我们如何找到一个这样的子段?请看上面的为伪代码endmax = max(endmax, 0) + a[i], 对于endmax它对应的子段的结尾显然是a[i],我们怎么知道这个子段的开头呢? 就看它有没有被更新。也就是说如果endmax’ = endmax + a[i]则对应子段的开头就是之前的子段的开头。否则,显然endmax开头和结尾都是a[i]了,让我们来改一下伪代码:

start = 1
answerstart = asnwerend = 1
endmax = answer = a[1]
for end = 2 to n do
	if endmax > 0 then
		endmax += a[end]
	else
		endmax = a[end]
		start = end
	endif
	if endmax > answer then
		answer = endmax
		answerstart = start
		answerend = end
	endif
endfor

这里我们直接用end作为循环变量,通过更新与否决定start是否改变。

总结:通过不断优化,我们得到了一个时间复杂度为 O(n),空间复杂度为O(1)的简单的动态规划算法。动态规划,就这么简单!优化无止境!

最后,我们来提供输入输出数据,由你来写一段程序,实现这个算法,只有写出了正确的程序,才能继续后面的课程。

输入

第1行:整数序列的长度N(2 <= N <= 50000)
第2 - N + 1行:N个整数(-10^9 <= A[i] <= 10^9)

输出

输出最大子段和。

输入示例

6
-2
11
-4
13
-5
-2

输出示例

20

请选取你熟悉的语言,并在下面的代码框中完成你的程序,注意数据范围,最终结果会造成Int32溢出,这样会输出错误的答案。

不同语言如何处理输入输出,请查看下面的语言说明。

 1 def max(a,b):
 2     if a>b:
 3         return a
 4     else:
 5         return b
 6
 7 n=int(input().split()[0])
 8 endmax=0
 9 ans=-10000000000
10 for i in range(n):
11     a=int(input().split()[0])
12     endmax=max(0,endmax)+a
13     ans=max(ans,endmax)
14
15 print(ans)
时间: 2024-10-12 10:34:26

动态规划2-最大子段和的相关文章

动态规划——A 最大子段和

A - 最大子段和 Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Status Description Given a sequence a[1],a[2],a[3]......a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7), the

ACM 动态规划 最大 m 子段和问题(课上)

Max Sum Plus Plus Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 23976 Accepted Submission(s): 8199 Problem Description Now I think you have got an AC in Ignatius.L's "Max Sum" problem. To be

2018.1.7 计算机算法课后习题总结

习题解答提要 习题1 1-1 分数分解算法描述 把真分数a/b分解为若干个分母为整数分子为"1"的埃及分数之和: (1) 寻找并输出小于a/b的最大埃及分数1/c: (2) 若c>900000000,则退出: (3) 若c≤900000000,把差a/b-1/c整理为分数a/b,若a/b为埃及分数,则输出后结束. (4) 若a/b不为埃及分数,则继续(1).(2).(3). 试描述以上算法. 解:设 (这里int(x)表示取正数x的整数),注意到 ,有 算法描述:令c=d+1,则

HDU 1024 Max Sum Plus Plus【动态规划求最大M子段和详解 】

Max Sum Plus Plus Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 29942    Accepted Submission(s): 10516 Problem Description Now I think you have got an AC in Ignatius.L's "Max Sum" problem

最大子段和 分治与动态规划

问题:  给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值.当所给的整均为负数时定义子段和为0,依此定义,所求的最优值为:    Max{0,a[i]+a[i+1]+…+a[j]},1<=i<=j<=n    例如,当(a1,a2,a3,a4,a4,a6)=(-2,11,-4,13,-5,-2)时,最大子段和为20. 问题求解: 方法一:枚举 学过程序设计的都会,那就是枚举i和j,求i和a[i]

HDOJ-1003 Max Sum(最大连续子段 动态规划)

http://acm.hdu.edu.cn/showproblem.php?pid=1003 给出一个包含n个数字的序列{a1,a2,..,ai,..,an},-1000<=ai<=1000 求最大连续子段和及其起始位置和终止位置,很基础的动态规划(DP)问题,看完DP第一次做的DP题目 DP真的是一种很优美的算法,或者说思想,但是比较难理解,我对DP的理解还很浅薄 # include <stdio.h> # define INF 1000000000 int main() { i

最大m子段和问题 Max Sum Plus Plus —— 动态规划

"最大m子段和" 问题 Max Sum Plus Plus 问题描述: 给定由n个整数(可能为负数)组成的序列a1,a2,a3--an,以及一个正整数m,要求确定此序列的m个不相交子段的总和达到最大.最大子段和问题是最大m字段和问题当m=1时的特殊情形. OJ题目源地址: http://acm.hdu.edu.cn/showproblem.php?pid=1024 Max Sum Plus Plus Time Limit: 2000/1000 MS (Java/Others)    M

动态规划——最大子段和

一.最大子段和 问题 给定N个数A1, A2, ... An,从中选出k(k不固定)个连续的数字 Ai, Ai+1, ... Ai+k-1,使得∑i+k−1iAt 达到最大,求该最大值. 分析     求最大子段和可以用多种算法来解决. (1)直接枚举 max = 0; for i in [1...n] for j in [i....n] sum = 0; for k in [i...j] sum += A[k] if(sum > max) max = sum //时间复杂度为O(n^3) (2

[ACM] POJ 2479 Maximum sum (动态规划求不相交的两段子段和的最大值)

Maximum sum Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 33363   Accepted: 10330 Description Given a set of n integers: A={a1, a2,..., an}, we define a function d(A) as below: Your task is to calculate d(A). Input The input consists o