(转)动态规划 - 最大子数组和(最大子序列和 | 连续子数组最大和)
原文作者:Yx.Ac 文章来源:勇幸|Thinking (http://www.ahathinking.com)
DP方案
考虑DP求解。这个问题可以继续像LCS、LIS一样,“从后向前”分析(《编程之美》对此又是从前向后分析的,个人不太习惯)。我们考虑最后一个元素arr[n-1]与最大子数组的关系,有如下三种情况:
- arr[n-1]单独构成最大子数组
- 最大子数组以arr[n-1]结尾
- 最大子数组跟arr[n-1]没关系,最大子数组在arr[0-n-2]范围内,转为考虑元素arr[n-2]
从上面我们可以看出,问题分解成了三个子问题,最大子数组就是这三个子问题的最大值,现假设:
- 以arr[n-1]为结尾的最大子数组和为End[n-1]
- 在[0-n-1]范围内的最大子数组和为All[n-1]
如果最大子数组跟最后一个元素无关,即最大和为All[n-2](存在范围为[0-n-2]),则解All[n-1]为三种情况的最大值,即All[n-1] = max{ arr[n-1],End[n-1],All[n-2] }。从后向前考虑,初始化的情况分别为arr[0],以arr[0]结尾,即End[0] = arr[0],最大和范围在[0,0]之内,即All[0]=arr[0]。根据上面分析,给出状态方程:
1 All[i] = max{ arr[i],End[i-1]+arr[i],All[i-1] }
代码如下:
1 /* DP base version*/ 2 #define max(a,b) ( a > b ? a : b) 3 4 int Maxsum_dp(int * arr, int size) 5 { 6 int End[30] = {-INF}; 7 int All[30] = {-INF}; 8 End[0] = All[0] = arr[0]; 9 10 for(int i = 1; i < size; ++i) 11 { 12 End[i] = max(End[i-1]+arr[i],arr[i]); 13 All[i] = max(End[i],All[i-1]); 14 } 15 return All[size-1]; 16 }
上述代码在空间上是可以优化为O(1)的,这里就不说了,详参考《编程之美》2.14。
下面说一下由DP而导出的另一种O(N)的实现方式,该方法直观明了,个人比较喜欢,所以后续问题的求解也是基于这种实现方式来的。
仔细看上面DP方案的代码,End[i] = max{arr[i],End[i-1]+arr[i]},如果End[i-1]<0,那么End[i]=arr[i],什么意思?End[i]表示以i元素为结尾的子数组和,如果某一位置使得它小于0了,那么就自当前的arr[i]从新开始,且End[i]最初是从arr[0]开始累加的,所以这可以启示我们:我们只需从头遍历数组元素,并累加求和,如果和小于0了就自当前元素从新开始,否则就一直累加,取其中的最大值便求得解。
到这里其实就可以了,在《编程之美》中,作者故意没有按照这种推导来实现(我猜的),而是在End[i-1]<0时,让End[i]=0,从而留出了一个问题(元素全是负数怎么办),其实如果按照上面的推导直接实现的话,就不存在这个问题了;(题外话:还是要坚持写博客做总结,这是一个重新思考的过程,这几天做这几道题发现当时也就看懂大部分,更多的细节和问题是在写博客重新整理的过程中发现的。后面扩展问题也是在整理过程中才发现了《编程之美》中的错误。)
基于上面的推导,代码如下:
1 /* DP ultimate version */ 2 int Maxsum_ultimate(int * arr, int size) 3 { 4 int maxSum = -INF; 5 int sum = 0; 6 for(int i = 0; i < size; ++i) 7 { 8 if(sum < 0) 9 { 10 sum = arr[i]; 11 }else 12 { 13 sum += arr[i]; 14 } 15 if(sum > maxSum) 16 { 17 maxSum = sum; 18 } 19 } 20 return maxSum; 21 }
其实上面的方法虽说是从DP推导出来的,但是写完发现也是很直观的方法,求最大和,那就一直累加呗,只要大于0,就说明当前的“和”可以继续增大,如果小于0了,说明“之前的最大和”已经不可能继续增大了,就从新开始,如此这样。
此题还有扩展,数组可以首位相连
posted on 2013-10-10 21:40 haoyancoder 阅读(281) 评论(0) 收藏 举报
浙公网安备 33010602011771号