动态编程据/h1>
动态规划据/strong>指的是一种解决问题的方法,在这种方法中,我们预先计算和存储更简单的、类似的子问题,以建立一个复杂问题的解。它类似于据a href="//www.parkandroid.com/wiki/recursion/" class="wiki_link" title="递归" target="_blank">递归据/a>,计算基本情况允许我们感应地确定最终值。当新值仅取决于先前计算的值时,此自下而上的方法很好。据/p>
通过动态编程解决的问题的一个重要属性是它应该具有重叠的子问题。这是区分DP的形式据a href="//www.parkandroid.com/wiki/divide-and-conquer/" class="wiki_link" title="分而治之" target="_blank">分而治之据/a>其中存储更简单的值并不需要。据/p>
为了表明该技术的强大,这里是通过动态编程通常接近的一些最着名的问题:据/p>
在竞赛环境中,动态编程几乎总是会出现(并且经常以一种令人惊讶的方式出现,无论参赛者对它多么熟悉)。据/p>
动机例子:改变硬币据/h2>
什么是值的最小数量据span class="katex">
你可以多次使用一个教派。据!-- end-problem -->
最优子结构据/strong>
这个问题鼓励我们通过动态规划来解决这个问题的最重要的方面是它可以简化为更小的子问题。据/p>
让据span class="katex">
记忆具有的递归据/h2>
我们已经声称,Naive递归是解决重叠子标数问题的不良方式。这是为什么?主要是因为所涉及的所有重新计算。据/p>
另一种避免这个问题的方法是第一次计算数据,然后以自顶向下的方式存储它。据/p>
让我们看看如何用记忆的方式来解决之前的硬币兑换问题。据/p>
1 2 3 4 5 6 7 8 9 10 11 12据/pre>def据/span>coinschange.据/span>(据/span>V.据/span>那据/span>V.据/span>):据/span>备忘录据/span>=据/span>{}据/span>def据/span>改变据/span>(据/span>V.据/span>):据/span>如果据/span>V.据/span>在据/span>备忘录据/span>:据/span>返回据/span>备忘录据/span>[据/span>V.据/span>]据/span>如果据/span>V.据/span>==据/span>0.据/span>:据/span>返回据/span>0.据/span>如果据/span>V.据/span>据据/span>0.据/span>:据/span>返回据/span>浮动据/span>(据/span>“正”据/span>)据/span>备忘录据/span>[据/span>V.据/span>]据/span>=据/span>闵据/span>([据/span>1据/span>+据/span>改变据/span>(据/span>V.据/span>-据/span>VI.据/span>)据/span>为了据/span>VI.据/span>在据/span>V.据/span>])据/span>返回据/span>备忘录据/span>[据/span>V.据/span>]据/span>返回据/span>改变据/span>(据/span>V.据/span>)据/span>
通过缓存动态编程vs递归据/strong>
动态编程据/em> | 递归与缓存据/em> |
如果访问了许多子问题,速度会更快,因为没有递归调用的开销据/td> | 直观的方法据/td> |
这个程序的复杂性很容易看出来据/td> | 只计算那些必要的子问题据/td> |
二维动态规划:实例据/h2>
我们会尝试在动态程序的帮助下解决这个问题,其中据strong>状态据/strong>,或描述问题的参数,由两个变量组成。据/p>
首先,我们建立一个二维数组据code>dp[开始][结束]据/code>其中每个条目解决了序列之间部分的指示问题据code>开始据/code>和据code>结束据/code>包容性。据/p>
我们试着想想当我们遇到一个新的据code>结束据/code>值,需要根据之前解决的子问题来解决新问题。以下是所有的可能性:据/p>
- 当据code>< =结束开始据/code>,没有有效的子序列。据/li>
- 当据code>b [end] <= k据/code>,即,最后一个条目是一个开放式括号,没有有效的子序列可以以其结束。有效地,如果我们根本没有包含最后一个条目,结果是相同的。据/li>
- 当据code>b[结束]> k据/code>,即,最后一个条目是一个结束括号,一个人必须找到最佳匹配,或者只是忽略它,以最大化总和的方式。据/li>
你能用这些想法来解决这个问题吗?据/p>
例如:最大路径据/h2>
通常情况下,动态规划有助于解决要求我们在隐式图设置中找到最有利可图(或成本最低)路径的问题。让我们试着用一个例子来说明这一点。据/p>
您应该从数字三角形的顶部开始,并通过在向左或向右下方的数字之间选择您的数字来选择您的通道。据/p>
您的目标是最大化符合您路径中的元素的总和。据/p>
例如,在下面的三角形中,红色路径使总和最大化。据/p>
去看据strong>最佳子结构据/strong>和据strong>重叠的子问题据/strong>请注意,每次我们从顶部到右下方或左下方移动时,我们仍然留下较小的数字三角形,就像这样:据/p>
我们可以用类似的方法分解每一个子问题,直到我们得到据em>边界情况据/em>底部:据/p>
在这种情况下,解决方案是据code>+马克斯(b, c)据/code>.据/p>
一个自下而上的动态规划解决方案是分配一个存储最大可达和的数字三角形据em>如果我们从那个位置开始据/em>.从下面一行开始计算数字三角形是很容易的据/p>
让我通过迭代来证明这一原则。据/p>
迭代1:据/p>
1据/pre>8 5 9 3据/code>
迭代2:据/p>
1 2据/pre>10 13 15 8 5 9 3据/code>
迭代3:据/p>
1 2 3据/pre>20 19 10 13 15 8 5 9 3据/code>
迭代4:据/p>
1 2 3 4据/pre>23 20 19 10 13 15 8 5 9 3据/code>
所以,我们从上面得到的最有效值是23,这就是我们的答案。据/p>