动态规划:青蛙跳台阶实践
一只青蛙要跳上有 n 级的台阶,每次只能跳 1 级或 2 级台阶,问青蛙总共有多少种不同的跳法?
暴力递归算法
我们按照最后一步是跳1阶还是跳2阶,将所有的方案划分成不重不漏的两个集合S(n−1)S(n-1)S(n−1)和S(n−2)S(n-2)S(n−2),两个集合方案数之和就是n阶的方案数。
子集合 S(n−1)S(n-1)S(n−1)(最后一步跳 1 级):跳到第 5 级前,已先到第 4 级,对应 “跳到第 4 级的所有跳法”:
- 1->2->3->4->5(最后 1 级:4→5)
- 1->2->4->5(最后 1 级:4→5)
- 1->3->4->5(最后 1 级:4→5)
- 2->3->4->5(最后 1 级:4→5)
- 2->4->5(最后 1 级:4→5)
子集合 S(n−2)S(n-2)S(n−2)(最后一步跳 2 级):跳到第 5 级前,已先到第 3 级,对应 “跳到第 3 级的所有跳法”:
- 1->2->3->5(最后 2 级:3→5)
- 1->3->5(最后 2 级:3→5)
- 2->3->5(最后 2 级:3→5)
这个问题我们要求出方案数,而非具体的方案,因此有
∣S(n)∣=∣S(n−1)∣+∣S(n−2)∣
|S(n)| = |S(n-1)| + |S(n-2)|
∣S(n)∣=∣S(n−1)∣+∣S(n−2)∣
简单而言
f(n)=f(n−1)+f(n−2)
f(n) = f(n-1) + f(n-2)
f(n)=f(n−1)+f(n−2)
代码写起来也非常的简单
def f(n):if n == 1:return 1if n == 2:return 2return f(n-1) + f(n-2)
在思考这个递归树的时候,有两种思路,一种就是f(n)
表示的是所有的方案,f(n-1)
是跳的n-1
阶的所有方案,f(n-2)
是跳到n-2阶的所有方案。另一种呢就是直接表示方案数,但是对我而言,这种比较难以理解,这种理解更偏向于数学抽象。
打印所有的方案
def f(n):"""生成青蛙跳上n级台阶的所有「台阶编号路径」每个路径是一个列表,包含依次经过的台阶编号(最终到达n)例如n=2时,返回[[1,2], [2]],对应路径0→1→2和0→2"""# 边界情况:0级台阶,无需跳跃,路径为空if n == 0:return [[]]# n=1:只有一种路径:0→1if n == 1:return [[1]]# n=2:两种路径:0→1→2 或 0→2if n == 2:return [[1, 2], [2]]res = []# 1. 从n-1级跳1级到n:所有到达n-1的路径后加narr1 = f(n-1)for path in arr1:res.append(path + [n])# 2. 从n-2级跳2级到n:所有到达n-2的路径后加narr2 = f(n-2)for path in arr2:res.append(path + [n])return res# 测试n=5
print("n=5的台阶编号路径:")
for i, path in enumerate(f(5), 1):print(f"方案{i}:{path}")
当然也可以直接打印
def print_frog_paths(n, current_path=None):"""直接打印青蛙跳上n级台阶的所有路径(台阶编号)参数:n: 目标台阶数current_path: 当前已走过的路径(用于递归传递)"""# 初始化当前路径if current_path is None:current_path = []# 递归终止条件:已到达目标台阶if n == 0:# 打印当前完整路径print("→".join(map(str, current_path)))return# 情况1:最后一步跳1级(如果可能)if n >= 1:print_frog_paths(n - 1, [n] + current_path )# 情况2:最后一步跳2级(如果可能)if n >= 2:print_frog_paths(n - 2, [n] + current_path)def main():try:n = int(input("请输入台阶的级数:"))if n < 0:print("台阶级数不能为负数!")elif n == 0:print("0级台阶无需跳跃!")else:print(f"青蛙跳上{str(n)}级台阶的所有路径:")print_frog_paths(n)except ValueError:print("请输入有效的整数!")if __name__ == "__main__":main()