动态规划入门
动态规划 Dynamic Programming (简称DP)
兔子树问题
题目:有一对兔子,从第一个月开始繁殖,每对兔子从出生后第二个月起,每个月都会生一对新兔子,假设,兔子寿命无限长
问题:一年后会有多少对兔子?
shell
第-2月 0
第-1月 1
第0月 1
第1月 2
第2月 3
第3月 5
第4月 8
第5月 13
菲波那切数列
第1项是0,第2项是1,从第3项开始,每一项是前两项之和
shell
f(0) = 0
f(1) = 1
f(2) = f(1) + f(0) = 2
f(3) = f(2) + f(1) = 3
f(4) = f(3) + f(2) = 5
f(5) = f(4) + f(3) = 8
f(6) = f(5) + f(4) = 13
...
f(n) = f(n-1) + f(n-2)
面试题:求菲波那切数列第n项
版本1:递归
实现代码
python
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
完整代码
python
# demo.py
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
def main():
n = 40
ret = fib(n)
print(n, ret)
if __name__ == "__main__":
main()
运行结果
shell
$ time python3 demo.py
40 102334155
python3 demo.py 14.26s user 0.08s system 97% cpu 14.651 total
存在问题:会重复计算,即:重叠子问题
当n越大,重复的子问题越多,浪费的时间也就越多
重叠子问题:递归过程中,同一个子问题被重复求解了多次
版本2:自顶向下的动态规划(记忆化)
动态规划方式一:递归 + 记忆化
记忆化搜索:缓存已经计算过的子问题的值,后续遇到相同的子问题时,直接查缓存
优化代码
python
def fib(n, memo={}):
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fib(n - 1, memo) + fib(n - 2, memo)
return memo[n]
完整代码
python
# demo.py
def fib(n, memo={}):
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fib(n - 1, memo) + fib(n - 2, memo)
return memo[n]
def main():
n = 40
ret = fib(n)
print(n, ret)
if __name__ == "__main__":
main()
运行结果
shell
$ time python3 demo.py
40 102334155
python3 demo.py 0.03s user 0.01s system 89% cpu 0.043 total
可以看到,时间从14.651s
优化到了0.043s
版本3:自底向上的动态规划(表格法)
动态规划方式二:不使用递归
表格法(Tabulation):从最小的子问题出发,逐步计算
优化代码
python
def fib(n):
if n <= 1:
return n
dp = [0, 1]
for i in range(2, n + 1):
dp.append(dp[i - 1] + dp[i - 2])
return dp[n]
完整代码
python
def fib(n):
if n <= 1:
return n
dp = [0, 1]
for i in range(2, n + 1):
dp.append(dp[i - 1] + dp[i - 2])
return dp[n]
def main():
n = 40
ret = fib(n)
print(n, ret)
if __name__ == "__main__":
main()
运行结果
shell
$ time python3 demo.py
40 102334155
python3 demo.py 0.03s user 0.02s system 89% cpu 0.050 total
版本4: 自底向上的动态规划(空间优化)
空间优化:不保存整个数组,空间复杂度,从O(n)
降低到了O(1)
优化代码
python
def fib(n):
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
完整代码
python
def fib(n):
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
def main():
n = 40
ret = fib(n)
print(n, ret)
if __name__ == "__main__":
main()
运行结果
shell
$ time python3 demo.py
40 102334155
python3 demo.py 0.03s user 0.01s system 87% cpu 0.049 total
总结
1、动态规划核心判断标准:
- 重复子问题
- 最优子结构:一个问题的最优解,可有由其子问题的最优解组合而成
2、适用题型:
- 爬楼梯
- 打家劫舍
- 最长公共子序列
- 零钱兑换
- 字符串拆分
- 0-1背包问题
3、不适用题型:
二分查找