题目1:求斐波那契数列的第n项
写一个函数,输入n,求斐波那契数列的第n项。
例:
输入0,输出0;输入1,输出1;输入10,输出55。
思路
经典的动态规划基础题。
动态规划分为两类:
1. 最优子结构
2. 公共子问题
斐波那契数列这题属于公共子问题。利用公式:f(n) = f(n-1) + f(n-2) 容易发现 f(n-1) 和 f(n-2) 求解过程中有大量重复的计算,如 f(n-3) 既会出现在f(n-1)的求解分支中,也会出现在 f(n-2) 的求解分支中。这就是公共的子问题。
对于解决公共子问题也有2种途径:
1. 备忘录法:增加缓存,如果缓存中已有解则直接调用,不再单独求解。
2. 自底向上构建:“真正的动态规划”,备忘录法显然增加了空间复杂度,对于此题可以从f(0) = 0 f(1) = 1 出发逐步求解出 f(n),而不是从f(n) 向下索解。
- 递归求解 f(n) = f(n-1) + f(n-2)
- 时间复杂度:T(n) = T(n-1) + T(n-2) + O(1) = O(2^n)
- 空间复杂度:递归深度O(n)
- 备忘录法 f(n) = f(n-1) + f(n-2)
- 时间复杂度:T(n) = T(n-1) + O(1) = O(n) (f(n-2) 已经在求f(n-1)时计算出)
- 空间复杂度:递归深度O(n),缓存O(n),总O(n)
- 自底向上,迭代求解。
- 时间复杂度:O(n)
- 空间复杂度:O(1)
- 矩阵乘法:
[ F ( n − 1 ) F ( n ) ] = [ 0 1 1 1 ] n × [ 0 1 ] \left[ \begin{array}{ccc} F(n-1) \\ F(n) \end{array} \right ]=\left[ \begin{array}{ccc} 0 & 1\\ 1 & 1 \end{array} \right ]^n\times\left[ \begin{array}{ccc} 0 \\ 1 \end{array} \right ] [F(n−1)F(n)]=[0111]n×[01]
斐波那契数列的计算可以利用上面的矩阵,每左乘一次矩阵相当于完成更新
[ 0 1 1 1 ] × [ a b ] = [ b a + b ] \left[ \begin{array}{ccc} 0 & 1\\ 1 & 1 \end{array} \right ]\times \left[ \begin{array}{ccc} a \\ b \end{array} \right ]=\left[ \begin{array}{ccc} b \\ a+b \end{array} \right ] [0111]×[ab]=[ba+b]
由于矩阵的幂运算,可以利用分治法 M(n) = M(n/2) * M(n/2) 加速。- 时间复杂度:T(n) = T(n/2) + O(1) = O(logn)
- 空间复杂度: 递归计算矩阵幂的深度O(logn)
代码
思路1:时间复杂度:O( 2 n 2^n 2n) ,空间复杂度:O(n)
def fibonacci_recursion(n):
"""
:param n: F(n)
:return: val
"""
if n == 0:
return 0
elif n == 1 or n == 2:
return 1
else:
return fibonacci_recursion(n-1) + fibonacci_recursion(n-2)
思路3:时间复杂度:O(n) ,空间复杂度:O(1)
def fibonacci_loop(n):
"""
:param n: F(n)
:return: val
"""
if n == 0:
return 0
cache = [0,1]
for i in range(n-2):
cache[0],cache[1] =cache[1], cache[0] + cache[1]
return cache[0] + cache[1]
思路4:时间复杂度:O(logn) ,空间复杂度:O(logn)
def fibonacci_matrix_mul(n):
"""
:param n: F(n)
:return: val
"""
if n == 0:
return 0
if n == 1:
return 1
def matrix_mul(matrix1, matrix2):
"""
:param matrix1:2*2 matrix
:param matrix2: 2*2 matrix
:return: 2*2 matrix
"""
(a11, a12), (a21, a22) = matrix1
(b11, b12), (b21, b22) = matrix2
c11 = a11 * b11 + a12 * b21
c12 = a11 * b12 + a12 * b22
c21 = a21 * b11 + a22 * b21
c22 = a21 * b12 + a22 * b22
mul_matrix =[[c11, c12], [c21, c22]]
return mul_matrix
def matrix_pow(mul,n):
"""
:param mul:2*2 matrix
:param n: pow n
:return: 2*2 matrix
"""
if n == 1:
return mul
if n == 2:
return matrix_mul(mul, mul)
temp = matrix_pow(mul, n // 2)
pow = matrix_mul(temp, temp)
if n % 2:
return matrix_mul(pow, mul)
else:
return pow
mul = [[0,1],[1,1]]
return matrix_pow(mul, n-1)[1][1]
思考
思路4在n较小时于思路3差距不大,但是当n = 1000000,差距明显。书作者说是由于其隐含的时间常数较大。但是如果考虑对于举证幂运算可以采用二分迭代而不是递归求解的话,可能可以加速。
问题2:青蛙跳台阶
一只青蛙一次可以跳上一级台阶,也可以跳上2级台阶。求该青蛙上一个n级台阶总共有多少种跳法。和LeetCode70如出一辙。
Leetcode 70. 爬楼梯
题目
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
思路
其实这个问题就是求斐波那契数列。
考虑当上n级台阶时,只有2种途径:1. 从n-1级迈一级 2. 从n-2级迈2级。
因此n级时的解等于n-1级的解加上n-2级的解,写出通项。
T(n) = T(n-1) + T(n-2)
显然,问题已经转化成我们求解过的斐波那契数列。
代码
略(见问题1)