有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。
要求用程序来求出一共有多少种走法。
有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。
要求用程序来求出一共有多少种走法。
这是通过找规律看出是递归的.
0 0
1 1
2
1 1
2
3
1 1 1
12
21
4
1 1 1 1
1 1 2
1 2 1
2 1 1
2 2
0 1 2 3 4
0 1 2 3 5
换种思路: 动态规划的思路
我们要走上第10级台阶, 最后一步肯定是从第8级或者第9级走的
所以走上第10级台阶,可以分为两部分.
从0到10级台阶的走法数量 = 0到9级再走一级台阶的走法数量 + 0到8级再走两级台阶的走法数量
这里会比较绕, 大概想,你会觉得,是偶,但是仔细想,不对啊。仔细想!
分治(Divide-and-Conquer Algorithm): 将问题划分成一些独立的子问题,递归的求解各子问题,
然后合并子问题的解而得到原问题的解
步骤:
分解(Divide):将原问题分解成一系列子问题;
解决(Conquer):递归地解决各个子问题。若子问题足够小,则直接求解。
合并(Combine):将子问题的结果合并成原问题的解。
如合并排序(Merge Sort).
动态规划(Dynamic Programming):
动态规划适用于子问题独立且重叠的情况,也就是各子问题包含公共的子子问题。在这种情况下,
若用分治法则会做许多不必要的工作,即重复地求解公共的子问题。动态规划算法对每个子子问题只求解一次,
将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案。
步骤:
描述最优解的结构
递归定义最优解的值
按自底向上的方式计算最优解的值
由计算出的结果构造一个最优解
适合采用动态规划方法的最优化问题中的两个要素:最优子机构和重叠子问题。
最优子机构:如果问题的一个最优解中包含了子问题的最优解,则该问题具有最优子机构。
重叠子问题:适用于动态规划求解的最优化问题必须具有的第二个要素是子问题的空间要很小,
也就是用来求解原问题的递归算法反复地解同样的子问题,而不是总是在产生新的子问题。
对两个子问题来说,如果它们确实是相同的子问题,只是作为不同问题的子问题出现的话,则它们是重叠的。
贪心算法:
对许多最优化问题来说,采用动态规划方法来决定最佳选择有点“杀鸡用牛刀”了,只要采用另一些更简单有效的算法就行了。
贪心算法是使所做的选择看起来都是当前最佳的,期望通过所做的局部最优选择来产生出一个全局最优解。
贪心算法对大多数优化问题来说能产生最优解,但也不一定总是这样的。
贪心算法只需考虑一个选择(亦即,贪心的选择);在做贪心选择时,子问题之一必须是空的,因此只留下一个非空子问题。
贪心算法与动态规划与很多相似之处。特别地,贪心算法适用的问题也是最优子结构。贪心算法与动态规划有一
个显著的区别,就是贪心算法中,是以自顶向下的方式使用最优子结构的。贪心算法会先做选择,在当时看起来是
最优的选择,然后再求解一个结果子问题,而不是先寻找子问题的最优解,然后再做选择。
贪心算法是通过做一系列的选择来给出某一问题的最优解。对算法中的每一个决策点,做一个当时看起来是最佳的选择。
这一点是贪心算法不同于动态规划之处。在动态规划中,每一步都要做出选择,但是这些选择依赖于子问题的解。
因此,解动态规划问题一般是自底向上,从小子问题处理至大子问题。贪心算法所做的当前选择可能要依赖于已经
做出的所有选择,但不依赖于有待于做出的选择或子问题的解。因此,贪心算法通常是自顶向下地做出贪心选择,
不断地将给定的问题实例归约为更小的问题。贪心算法划分子问题的结果,通常是仅存在一个非空的子问题。
本题来说:
动态规划的三个重要概念:
最优子结构、边界、状态转移公式
f(8)和f(9)是f(10)的最优子结构(f(8) + f(9) = f(10))
f(1)、f(2)是问题的边界,不需要计算的.
f(n) = f(n-1) + f(n-2)是阶段和阶段之间的状态转移方程
__
#!/usr/bin/python
# -*- coding:utf-8 -*-
import functools
from timeit import default_timer
def run_time(f: "builtin function or method"):
@functools.wraps(f)
def inner(*args, **kwargs):
start = default_timer()
result = f(*args, **kwargs)
print(f"result in {f.__name__}: {result}")
end = default_timer()
print(f"run time in {f.__name__}: {end - start}")
return result
return inner
def cache(f: "builtin function or method"):
store = {}
@functools.wraps(f)
def _(n):
if n in store:
return store[n]
else:
res = f(n)
store[n] = res
return res
return _
class Solution:
@staticmethod
@run_time
def recursive_jump(level: int) -> int:
# 可以看到,如果不保存以前的递归结果的话,复杂度是O(n^2)
# 但是这里用了缓存,所以数量级基本和下面的一样
@cache
def _recursive_jump(input_steps: int) -> int:
if input_steps < 3:
return input_steps
return _recursive_jump(input_steps - 1) + _recursive_jump(input_steps - 2)
return _recursive_jump(level)
# 可以看到,时间空间复杂度是O(n)
# 这是动态规划最简单的题,当然你可以当做是把上面的递归写成迭代形式了而已
@staticmethod
@run_time
def dp_jump(level: int) -> int:
if level <= 0:
return level
a, b = 1, 2
for i in range(1, level):
a, b = b, a + b
return a
if __name__ == '__main__':
solution = Solution()
solution.dp_jump(30)
level_steps_mapping = dict()
solution.recursive_jump(30)
result in dp_jump: 1346269
run time in dp_jump: 1.4899999999998248e-05
result in recursive_jump: 1346269
run time in recursive_jump: 2.4499999999996747e-05
可以看到,经过缓存后的递归实际上和迭代性能在数量级上实际上没有什么区别的.