【问题标题】:How can I find the maximum sum of a sub-sequence using dynamic programming?如何使用动态编程找到子序列的最大和?
【发布时间】:2012-01-28 19:23:37
【问题描述】:

我正在重新阅读 Skiena 的算法设计手册,以了解一些我从学校开始就忘记的东西,我对他对动态编程的描述感到有些困惑。我在 Wikipedia 和其他各种网站上查找了它,虽然这些描述都很有道理,但我自己却无法找出具体的问题。目前,我正在研究 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) 动态规划解决方案是什么?我觉得我几乎明白了,但我错过了一些东西。

【问题讨论】:

    标签: algorithm dynamic-programming


    【解决方案1】:

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

    以下伪代码的解释也在pdf中。

    【讨论】:

    • 我对图表感到困惑,因为它跳过了几个子序列(例如 [5, 15] 和 [15, -30])。但我会通读 PDF,看看它是否更有意义。谢谢!
    • 好的,读完之后,现在更有意义了。非常感谢!
    • @cMinor 链接已损坏。
    • 经过大量搜索,这看起来像是为数不多的具有正确定义的地方之一——但链接已损坏
    • 如果所有数字都是负数,给定的伪代码将无法工作。
    【解决方案2】:

    有一种解决方案,首先将数组排序到一些辅助内存中,然后对原始数组和排序后的数组应用最长公共子序列方法,其中公共子序列的总和(不是长度) 2 个数组作为表中的条目(记忆)。这样也能解决问题

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

    当记忆出现时,这不是一个好的解决方案。这只是为了让我们一瞥如何将问题简化为另一个问题。

    【讨论】:

      【解决方案3】:

      我对DP的理解是关于“制作一张桌子”。其实DP中“编程”的原意就是简单的做表。

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

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

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

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

      max 中的第一个词是“不包含 a[i]”,第二个词是“包含 a[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)) 
      

      结果:在i遍历数组a之后,子序列的最大和应该是dp表中最大的项。但仔细看看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])    
      

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

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

      这肯定比在 i-loop 内的 for j in range(0,i): 和所有可能的子数组 sum 的简单方法 O(n^2)*O(n) 更好。

      一个小警告,因为设置dp[0]的方式,如果a中的所有项目都是负数,我们不会选择任何项目。所以对于最大和连续子数组,我们把它改成

      dp[0] = a[0]
      

      【讨论】:

      • 顺便说一句,它是max(dp) 而不是dp[-1] 是因为子序列或子数组可能不包含数组的最后一个元素。
      猜你喜欢
      • 1970-01-01
      • 2018-08-10
      • 1970-01-01
      • 1970-01-01
      • 2014-03-23
      • 2015-01-09
      • 1970-01-01
      • 2011-06-24
      • 1970-01-01
      相关资源
      最近更新 更多