我正在参加「掘金·启航计划」
什么是动态规划?
动态规划(dp)问题是通过求子问题解的方式,来解决原来复杂或庞大的问题。
通俗的来讲,动态规划问题都可以用方程的形式进行表达,解决dp问题的关键也在于找出问题潜在的方程。
比如下图中,有一个机器人在m*n的网格的左上角,它要到达右下角的目的地,但每一次只能向下或者向右走,你能算出有多少条不同的路径吗?
这种问题使用暴力枚举在一些情况下可能也能解决,但是会花费大量的时间和消耗大量的内存。而且在力扣或牛客等,暴力法通常是不会ac的。
动态规划问题解法
首先,有两种方法:
- 自下而上
- 自上而下
1. 自下而上
自下而上是通过 迭代 实现的:
自下而上就是先从子问题开始计算,重复计算推算出问题的解。
斐波那契数列[1、1、2、3、5、8、13、21、34、……]以如下递推的方法定义:
F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
如果求F(n)的值,在已知F(0)=0, F(1)=1时,通过F(0)和F(1)计算F(2),然后使用计算结果计算F(3)…
重复计算可以求解F(n)。
// 伪代码如下:
F = array of length (n + 1)
F[0] = 0
F[1] = 1
for i from 2 to n:
F[i] = F[i - 1] + F[i - 2]
2. 自上而下
自上而下通过 递归 实现,并且通过 记忆化 提高效率(其实就是递归+记忆化搜索)
继续以斐波那契数列的递推公式为例,如果我们想求解F(n),我们需要求解F(n-1)和F(n-2);求解F(n-1)需要F(n-2)和F(n-3) ,求解F(n-2)需要F(n-3) 和F(n-4)…一直递归到已知的F(0)=0和F(1)=1
自上而下的缺点也很明显,在计算中存在了大量的重复计算,导致效率不高
解决方法也很简单,用空间换时间,也就是记忆化:将函数调用的结果存储在哈希图或数组中,这样当再次进行相同的函数调用时,我们可以返回记忆的结果,避免重复计算。
//伪代码如下:
memo = hashmap
Function F(integer i):
if i is 0 or 1:
return i
if i doesn't exist in memo:
memo[i] = F(i - 1) + F(i - 2)
return memo[i]
总结
可能有人要问:“这两个方法哪个更好呢?”
动态规划问题可以用二者任意一种方法实现,每个方法都有各自的优点:
- 自下而上的运行速度更快 (递归效率低)
- 自上而下的实现更简单 (使用递归,我们不用在意子问题的逻辑顺序;而自下而上的方法中,我们需要解决子问题的逻辑顺序)
文章开头问题及图片的出处:《力扣 62.不同路径》,dp问题非常经典的一道题目,感兴趣的小伙伴可以尝试一下。
祝你今天愉快,你明天的愉快留着我明天再祝。——王小波