《剑指offer31》:连续子数组的最大和

这是《剑指offer》系列的第一篇,这个系列的文章主要用于记录看书时的一些感想,做个记录。

 

题目

输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。例:

思路

这里采用动态规划的思想:

  • 第一步:做一个选择,基于这个选择产生一个或多个待解的子问题

这里用函数 f(i) 表示以第 i 个数字结尾的子数组的最大和,其中 0\leq i< n。注意到函数 i 的不同取值构成了原题可能解的全集。例:当 i = 2 的时候,有 f(2) = 1 + (-2) + 3 = 2  。

这里我们选择子数组的结束位置 i = 6 ,并且假定这个选择是最优解。即:我们假设结束于下标为 6 的子数组的最大和是原数组中所有子数组的最大和。

选择了结束位置之后,数组下标 6 的右边不用再进行考虑。那么基于 f(6) 产生子问题 f(6 - 1) ,意义是结束于下标为  5 的子数组的最大和。

  • 第二步:证明问题具有最优子结构性质,并得到递归式

使用“剪切 - 粘贴”反证法,证明在 f(5)>0 的条件下问题具有最优子结构性质:

假设 f(5) 不是数组区间 Data[0, 5] 内结束于下标为 5 的子数组的最大和,那么我们将 f(5) 从 f(6) 中剪切去掉,并把最优解 f^*(5) 粘贴其中得到  f^*(6), 则有 f^*(6) > f(6),与“ f(6) 是最优解”矛盾,故原假设不成立,得证。

这里稍微有点绕,如下图:

这里我们证明的是: f(5) 区间在 f(5)> 0 的时候一定会被f(6) 区间包含。

注:这里约束 f(5)> 0 的意义在于,当子问题的解是负数的时候,即使它是子问题能达到的最大值,也会拖了原问题的后腿,还不如直接等于 Data[i] 。例如 f(2) 的子问题 f(1) = 1 + (-2) = -1​ ​​​​​​,那结束于下标为 2 的子数组的最大和应该等于 Data[2] 本身,即 f(2) = 3 。这是来自于我们定义这个函数时隐含的约束 

由此我们证明了原问题的最优解 “ 结束于下标为 6 的子数组的最大和 ” 包含了它的子问题 “ 结束于下标为 5 的子数组的最大和 ” 的最优解。我们称这个问题具有最优子结构。

得到以下递推式:

f(i)=\left\{\begin{array}{ll} { \text {Data}[i]} & {i=0 \text { or } f(i-1) \leq 0} \\ {f(i-1)+\text {Data[i] }} & {i \neq 0 \text { and } f(i-1)>0} \end{array}\right.

  • 第三步:证明问题具有重叠子问题性质

如果递归算法反复求解相同的子问题,我们称最优化问题具有重叠子问题性质,这里我们采用子问题图来可视化子问题之间的依赖关系,其中节点代表子问题,弧代表调用关系:

例如规模为4的子问题在求解时会调用规模为3、2和1的子问题,而处理规模为3的子问题的时候又会调用规模为2和1的子问题,造成高昂的计算代价。

  • 第四步:使用动态规划进行求解

动态规划求解分为带备忘录的自顶向下法带表格的自底向上法。

以下附上剑指offer提供的自底向上法:

结语

动态规划的题目常见,做多了题,有时候稀里糊涂地套上去试一下能做出来。但一直不懂背后的原理。这篇主要是用《算法导论》里面的分析手法分析了下《剑指offer》里的题,得到了些粗浅的看法,以后还要多加练习。

 

Reference:

《算法导论》p216

《剑指offer》p171

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章