第三章上机实践报告
1、实践题目
7-2 最大子段和
给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时,定义子段和为0。
要求算法的时间复杂度为O(n)。
2、问题描述
由于要求时间复杂度是O(n),因此递归的方法被砍,只能另外谋求生计
当时间复杂度为O(n),第一反应是用一个for循环,但是如何能只用一个for循环得出结果呢?
自然想到在循环遍历的时候将当前最优解存起来,最后进行一个比较,选择最好的那个解
于是就用到动态规划法来解决问题。
3、算法描述
算法的核心在于递归方程
MaxSum[i] = ele[i] i=0 MaxSUm[i] = max{ele[i], ele[i] + MaxSum[i - 1]} 0 < i < n
因此着重讲解递归方程的求得过程
首先对原输出和辅助记录空间进行定义
ele[i]:序列的第i - 1个元素,i从0开始(数组下标) MaxSum[i]:从ele[0],以ele[i]为尾数的最大子段和(可能不包括ele[0],ele[1]....ele[i - 1])
然后我们分析最开始的情况,从第一个数开始
第一个数有两种情况,小于等于或0大于0。若小于等于0,则最大子段和必不包括它,因为它是第一个数。如果是大于0,则最大子段和有可能包括它。
然后到第二个数,先不管第二个数的正负,根据上一条,如果第一个数ele[0]大于0,那么序列{ele[0],ele[1]}对应的MaxSum[1]=ele[0]+ele[1];如果ele[0]小于0,对应的MaxSum[1]=ele[1],及最大子段和为本身,因为前面只有一个数还小于0,所以必不要它
再看第三个数,在这时候我们将前面的所有数,不管有多少个(现在是两个),都抽象成“前面的数”,它的值为MaxSum[i-1]。那么我们的第三个数就会变成“前面的数”的后面那个数,也就变成了“第二个数”。那么根据上面第二个数的判断,如果“前面的数”小于0,那么“前面的数”都不要了,其最大子段和为本身;反之,最大子段和为“前面的数”加上本身。
也就是说,当前我们看第i个数ele[i],这个数之前的最大子段和为MaxSum[i - 1],如果MaxSum[i - 1] < 0,那么以ele[i]为尾数的最大子段和MaxSum[i]必为ele[i],否则,其为
为统一,我们将第一个数的最大子段和定义为其本身,即:
MaxSum[0] = ele[0]
注意,这里有个坑,就是以ele[i]结尾的序列,他的最大子段和不一定包括ele[i]。很多在上面的推导过程中都发现了,序列{ele[0],ele[1]}的最大序列可以是ele[0](ele[0]>0, ele[1] < 0)。 而我们MaxSum存的是以ele[i]为尾数的最大子段和,因此MaxSum的最后一个数MaxSum[n]并非最终答案。序列{ele[0],ele[1]...ele[n]}的最大子段和可能为{ele[i],ele[i+1]...ele[j]}(0<i<j<n),那么最终答案应该是MaxSum[j]。
因此我们需要额外一个变量来存放MaxSum中的最大值(当然你也可以得到MaxSum后遍历找最大)。
4、算法时间及空间复杂度
时间复杂度
T(n) = O(n)
空间复杂度
S(n) = O(n)
5、心得体会
感觉动态规划是分治法的进阶版本,基本都用到了一个表作为辅助,避免了一个问题重复计算,节省了许多资源。当然,感觉想要好好利用好动态规划,要弄清楚的、很重要的一点,就是如何填表。填表的方式有很多,是从右上往左下,还是别的,以及要弄清楚,作为辅助表,应该怎样和存储数据的表关联在一起,弄清楚两者的关系,弄清楚这个表里存的数据是什么,有什么用,最终要输出什么,才是最重要的。
当然,动态规划,在弄清楚表的作用之后,就要弄清楚,如何写递归方程,在写好递归方程之后,代码就能很迅速的写出来,同时能保证较高的正确率,在打题时基本都能一次性通过。