我正在尝试学习动态编程(DP)的基础知识,并经历了一些我可以获得的在线资源,例如......
直到现在我才明白
动态问题几乎与 只有差异的递归(赋予它知道的力量 作为)
即。 存储我们获得的价值或解决方案并再次使用它来寻找下一个解决方案
例如:
根据codechef的解释
问题:最低步数
问题陈述:在正整数上,您可以执行以下3个步骤中的任何一个。
现在问题是,给定正整数n,找到n到1的最小步数
例如:
对于n = 7,输出:3(7 -1 = 6/3 = 2/2 = 1)
int memo [n + 1]; //我们将元素初始化为-1(-1表示,尚未解决)
针对上述问题的自上而下的方法
int getMinSteps ( int n ) {
if ( n == 1 ) return 0;
if( memo[n] != -1 ) return memo[n];
int r = 1 + getMinSteps( n - 1 );
if( n%2 == 0 ) r = min( r , 1 + getMinSteps( n / 2 ) );
if( n%3 == 0 ) r = min( r , 1 + getMinSteps( n / 3 ) );
memo[n] = r ; // save the result. If you forget this step, then its same as plain recursion.
return r;
}
我理解dp是否正确,或者任何人都能以更好更简单的方式解释它,这样我就可以学习它并且可以解决动态编程的问题。
答案 0 :(得分:3)
维基百科的Fibonacci sequence示例给出了一个很好的例子。
动态编程是一种优化技术,它将潜在的指数递归解转换为多项式时间解,假设问题满足principle of optimality。基本上意味着您可以从最佳子问题构建最佳解决方案。 动态规划易处理的问题的另一个重要特征是它们是overlapping。如果将这些问题分解为重复的子问题,则可以重复使用相同的解决方案来解决这些子问题。 最优子结构属性和重叠子问题的问题,动态编程是一种解决它的潜在有效方法。
在这个例子中,您可以看到Fibonacci数的递归版本会在树状结构中增长,这表明存在指数爆炸。
function fib(n)
if n <=1 return n
return fib(n − 1) + fib(n − 2)
因此,对于fib(5)
,你得到:
fib(5)
fib(4) + fib(3)
(fib(3) + fib(2)) + (fib(2) + fib(1))
等等像时尚一样的树。
动态编程允许我们在多项式时间内使用最优子问题逐步构建解决方案。这通常通过某种形式的记录保存来完成,例如表格。 请注意,存在子问题的重复实例,即一次计算fib(2)就足够了。
同样来自维基百科,一种动态编程解决方案
function fib(n)
if n = 0
return 0
else
var previousFib := 0, currentFib := 1
repeat n − 1 times // loop is skipped if n = 1
var newFib := previousFib + currentFib
previousFib := currentFib
currentFib := newFib
return currentFib
此处的解决方案是从最初设置的previousFib
和currentFib
构建的。 newFib
是根据此循环中的先前步骤计算的。 previousFib
和currentFib
代表我们对之前子问题的记录。
结果是一个多项式时间解(在这种情况下为O(n)),对于递归公式将是指数的问题(在这种情况下为O(2 ^ n))。
答案 1 :(得分:0)
一个很棒的答案How should I explain dynamic programming to a 4-year-old? 只是在这里引用相同:
在一张纸上写下“ 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 =“
“那等于什么?”
计数“八!”
在左侧写下另一个“ 1 +”
“那呢?” 迅速“九!”
“您怎么知道它快九点了?”
“您刚刚添加了一个”
“因此您无需重新计数,因为您记得有 八! 动态编程只是一种表达“记住”的奇特方法 可以节省时间的东西'”