如何使用动态编程找到子序列的最大总和?

时间:2011-12-27 22:15:34

标签: algorithm dynamic-programming

我正在重新阅读Skiena的算法设计手册,以了解自学校以来我忘记的一些东西,我对他对动态编程的描述感到有些困惑。我在维基百科和其他各种网站上查了一下,虽然描述都很有意义,但我自己也难以找出具体的问题。目前,我正在从Skiena的书中解决问题3-5。 (给定n个实数的数组,在输入的任何连续子向量中找到最大总和。)我有一个O(n ^ 2)解,如this answer中所述。但我坚持使用动态编程的O(N)解决方案。我不清楚复发关系应该是什么。

我看到子序列形成一组总和,如下:

S = {a,b,c,d}

a    a+b    a+b+c    a+b+c+d
     b      b+c      b+c+d
            c        c+d
                     d

我没有得到的是如何选择哪一个在线性时间内最大。到目前为止,我已经尝试过跟踪最大值的事情,如果当前值为正,则将其添加到总和中。但是当你有更大的序列时,这就成了问题,因为可能有一些负数会减少总和,但是后来的大正数可能会使它恢复到最大值。

我也想起了总计区域表。你可以只使用累积和来计算所有的总和:a,a + b,a + b + c,a + b + c + d等等(例如,如果你需要b + c,它只是(a +) b + c) - (a)。)但是没有看到O(N)的方法来获得它。

有人可以向我解释一下这个特定问题的O(N)动态编程解决方案是什么吗?我觉得我几乎得到了它,但我错过了一些东西。

3 个答案:

答案 0 :(得分:11)

你应take a look to this pdf回到学校http://castle.eiu.edu这里:

enter image description here

以下伪代码的解释也在pdf中。 enter image description here

答案 1 :(得分:1)

有一个解决方案,首先将数组排序到某个辅助内存中,然后将最长公共子序列方法应用于原始数组和排序数组,其中包含公共子序列的总和(不是长度) 2个数组作为表的入口(Memoization)。这也可以解决问题

总运行时间是O(nlogn)+ O(n ^ 2)=>为O(n ^ 2) 空间是O(n)+ O(n ^ 2)=>为O(n ^ 2)

当内存进入画面时,这不是一个好的解决方案。这只是为了让人们了解如何将问题相互减少。

答案 2 :(得分:0)

我对DP的理解是关于“制作一张桌子”。事实上,DP中原来的“编程”意思就是制作表格。

关键是找出要放在表格中的内容,或现代术语:要跟踪的状态,或者DAG中的顶点键/值是什么(如果它们听起来很奇怪,请忽略这些术语你)。

如何选择dp[i]表是数组索引 i 的最大总和,例如,数组为[5,15,-30,10]

第二个重要的关键是“最佳子结构”,即“假设”dp[i-1]已经存储了以索引 i-1 结尾的子序列的最大总和,这就是为什么 i 的唯一步骤是决定是否将a[i]包含在子序列中

dp[i] = max(dp[i-1], dp[i-1] + a[i])

max中的第一项是“不包括[i]”,第二项是“包括[i]”。请注意,如果我们不包含a[i],则到目前为止最大的总和仍然是dp[i-1],它来自“最佳子结构”参数。

所以整个程序看起来像这样(在Python中):

a = [5,15,-30,10]

dp = [0]*len(a)
dp[0] = max(0,a[0]) # include a[0] or not

for i in range(1,len(a)):
    dp[i] = max(dp[i-1], dp[i-1]+a[i]) # for sub-sequence, choose to add or not     


 print(dp, max(dp)) 

结果:在dp遍历数组i之后,最大的子序列总和应该是a表中的最大项。但仔细看看dp,它掌握了所有信息。

因为它只遍历数组a中的项目,所以它是一个O(n)算法。

这个问题看起来很愚蠢,因为只要a[i]为正,我们就应该总是把它包含在子序列中,因为它只会增加总和。这种直觉与代码匹配

dp[i] = max(dp[i-1], dp[i-1] + a[i])

所以最大。子序列问题的总和很容易,根本不需要DP。简单地说,

sum = 0
for v in a:
     if  v >0
         sum += v

然而,“连续子阵列”问题的最大总和呢?我们需要改变的只是一行代码

dp[i] = max(dp[i-1]+a[i], a[i])    

第一项是“在连续子阵列中包含[i]”,第二项是决定开始一个新的子阵列,从[i]开始。

在这种情况下,dp[i]是最大值。求和以index-i结尾的连续子数组。

这肯定比天真的方法O(n ^ 2)* O(n),i-loop内的for j in range(0,i):sum所有可能的子阵列都要好。

一个小警告,因为设置dp[0]的方式,如果a中的所有项都是否定的,我们就不会选择任何项。因此,对于max sum连续子阵列,我们将其更改为

dp[0] = a[0]