动态规划问题
文章目录
先来看个老套的例子,斐波那次序列:0,1,1,2,3,5,8,13,21
后一个数等于前两个数的和。
如果求第n个数是几?
动态规划
按照常理的计算方式,就是从左到右依次求和就可以得到n, 即:f(0)+f(1)=f(2),然后f(1)+f(2)=f(3)一直到f(n-2)+f(n-1)=f(n) 用Golang代码实现如下:
|
|
其中定义前后两个变量,依次代表f(n-2)和f(n-1)的值。 这种安装人脑常理的计算方式就是 动态规划 了,那么我们再看如果是计算机思维的解答方式 递归
递归
既然f(n)=f(n-1)+f(n-2),那么f(n-1)呢,是不是就等于f(n-1)=f(n-1 -1)+f(n-1 -2),这里我故意用空格隔开了后边的一个减号,意思就是这里的n-1
可以看成一个整体
用 n 代替 n-1 ,就成了f(n)=f(n-1)+f(n-2)。从数学公式上来说就是同一个函数,也就是说f(n-1)的子问题可以用和f(n)一样的方法解决。
暴力递归
先来看看代码如何实现:
|
|
代码很像公式且测试方法能得到正确的结果。
如图,其中f(0),f(1)…f(n-2)等都被重复计算了多次,虽然函数写起来简单但有好多的重复计算,能否避免呢?
记忆化递归
当然可以。用一个数组保存计算过的f(n-2),f(n-1)不就可以了,代码如下:
|
|
你可以打开FibonacciWithCache
中的输出可以看到dp数组的变化过程,最后的dp是dp: [0 0 1 2 3 5 8 13 21 34 55]
,值得注意的是
因为递归的是n所以数组的长度需要比n多一个,避免索引溢出。
总结
记忆化递归和动态规划的本质思想是一致的,是对斐波那契数列定义的不同表现形式:
- 记忆化递归 从顶至低: 求 f(n)f(n) 需要 f(n - 1)f(n−1) 和 f(n - 2)f(n−2) ; \cdots⋯ ;求 f(2)f(2) 需要 f(1)f(1) 和 f(0)f(0) ;而 f(1)f(1) 和 f(0)f(0) 已知;
- 动态规划 从底至顶: 将已知 f(0)f(0) 和 f(1)f(1) 组合得到 f(2)f(2) ;\cdots⋯ ;将 f(n - 2)f(n−2) 和 f(n - 1)f(n−1) 组合得到 f(n)f(n) ;
斐波那契数列问题不包含「最优子结构」,只需计算每个子问题的解,避免重复计算即可,并不需要从子问题组合中选择最优组合。 如果想进一步了解,请移步Krahets写的的最优子结构示例:蛋糕最高售价
文章作者 古道
上次更新 2022-02-06