问题描述:给定一整数序列A1, A2,... An (可能有负数),求A1~An的一个子序列Ai~Aj,使得Ai到Aj的和最大
这是我前几天参加新浪广告算法组面试的时候一个题目。自己当时脑袋浆糊了,加上前面答的不好,这里答的时候脑子一片空白。哎~
这一题本质在于动态规划的运用。至于O(n^3), O(n^2),还有O(nlogn)的解法我就不多说了。
先上代码
int FindMaxSumSubSequence(int a[], int len) { int max = 0; int sum = 0; for(int i = 0; i < len; i++) { sum += a[i]; if ( sum > max ) max = sum; else if ( sum < 0) sum = 0; } return max; }
关键在于如何理解这样一段代码。
网上大众的解释如下
“在这一遍扫描数组当中,从左到右记录当前子序列的和temp_sum,若这个和不断增加,那么最大子序列的和max也不断增加(不断更新max)。如果往前扫描中遇到负数,那么当前子序列的和将会减小。此时temp_sum 将会小于max,当然max也就不更新。如果temp_sum降到0时,说明前面已经扫描的那一段就可以抛弃了,这时将temp_sum置为0。然后,temp_sum将从后面开始将这个子段进行分析,若有比当前max大的子段,继续更新max。这样一趟扫描结果也就出来了。”
这是一种比较通俗化的描述,但没有深入动态规划的本质解释。尤其是如何解释“说明前面已经扫描的那一段就可以抛弃了”这样一句话。
下面将从动态规划的角度解释为什么可以在O(n)复杂度内解决问题
根据定义,我们可以定义bj
那么最大子段和为
由bj的定义知,b[j-1] > 0 时, b[j] = b[j-1] + a[j],否则b[j] = a[j]
故状态转移方程为b[j] = max{b[j-1]+a[j], a[j]}
故代码如下
int MaxSum(int a[], int len) { int sum = 0; int b = 0; for(int i = 0; i < n; ++i) { if ( b>0 ) b+=a[i]; else b = a[i]; if ( b > sum ) sum = b; } return sum; }
写到这里 ,还需要解释几个比较让人迷惑的问题
1) b[j]到底是个什么东西?
b[j]本质上就是到下标为j的地方为止,连续子序列的最大和。
那么整个数组的最大子序列和就是max(b[i])
2) b[j]具有最优子结构么
当然,任意一个b[j]均由b[j-1]扩展而来,为什么,请结合问题1)。扩展的方式如上文所述。故可以用状态转移方程统一描述。
3) 为什么b[j-1] > 0 b[j] = b[j-1] + a[j],否则b[j] = a[j]
看图,如果是负数,那么在此基础上扩展,和一定会变小,与其如此,不如选定新的和,即b[j] = a[j]