一个
阶的楼梯,每次能走 1~2 阶,问走到
阶一共多少种走法
因为每次能走 1~2 阶,所以第
阶的前一个状态可以是第
阶,或第
阶
,且只有这两种情况。
以
表示到达第
阶的所有走法个数,当
时,则有:
当
时,处于原地,因为步长为 1 ~ 2 阶,不能有前进之后再后退的情况,所以只能有当前一种方式,所以
当
时,只能选择步长为 1 一种方式,所以
根据此推到公式可以有如下解法。
def climbingStairs(n):
if n == 0 or n == 1:
return 1
return climbingStairs(n-1) + climbingStairs(n-2)
递归形式的时间复杂度为
,空间复杂度为
。
时间复杂度参考斐波那契数列的时间复杂度 Time complexity of recursive Fibonacci program, 因为递归栈数最深为
层,所以空间复杂度为
。
递归形式算法的时间复杂度呈指数级增长,所以这种算法较为不现实。观察递推关系式:
可以发现,每个阶梯数的函数值,只与其前两项的函数值有关,所以不妨由低到高进行推导。
def climbingStairs(n):
a, b, i = 1, 1, 2
while i <= n:
a, b, i = b, a + b, i + 1
return b
以
表示
,迭代更新
的值,即可得出最后的结果。时间复杂度为
,空间复杂度为
。
根据递推关系式,对二阶差分方程构造矩阵:
根据
的递推关系式, 可得:
import numpy as np
import math
def climbingStairs_matrix(n):
unit = np.array([[1, 1], [1, 0]])
target, result = n - 1, np.identity(2, int) # target means the total index
while target > 1:
index, tmp = 1, unit
times = int(math.log2(target)) # the iterations times
while index <= times:
tmp, index = np.dot(tmp, tmp), index + 1
result = np.dot(tmp, result)
target = target - 2 ** times
result = np.dot(unit, result) if target == 1 else result
result = np.dot(result, np.array([[1], [1]]))
return result[0][0]
以求
值为例,若
,则有如下分析:
若已知
,因为
,则只需要一次计算即可;
若已知
,因为
,则得出
值需要两次计算,首先计算出
,然后计算出
; ... ... ... 若已知
,则计算出
需要
次计算,即计算
值的时间复杂度为
即最好情况下矩阵运算的时间复杂度为
,空间复杂度为
。
以求
值为例,若
,则有:
由最好情况分析结论知,
的计算次数为
。若已知
,则得出
的值需要
次计算。
递推有:
由最好情况分析结论知,
的计算次数为
。若已知
,则得出
的值需要
次计算。 ... ... ...
则得出
的值需要
次计算。
即最坏情况下矩阵运算的时间复杂度为
,空间复杂度为
。
若使用逆矩阵,则逆矩阵的个数
存在同样问题,所以此处不涉及逆矩阵运算。
观察以上矩阵形式的代码可知,非最优场景下的计算过程存在重复运算,虽然通过对数形式降低了重复的次数,但依然存在计算能力的浪费。针对该情况,申请空间保留中间计算状态。
def climbingStairs_matrix(n):
unit = np.array([[1, 1], [1, 0]]) # M represents the unit matrix
arr, target, result = [unit], n - 1, np.identity(2, int) # target means the total index
while target > 1:
index, tmp = 1, unit
times = int(math.log2(target)) # the iterations times
if times >= len(arr):
while index <= times:
tmp, index = np.dot(tmp, tmp), index + 1
arr.append(tmp)
else:
tmp = arr[times]
result = np.dot(tmp, result)
target = target - 2 ** times
result = np.dot(unit, result) if target == 1 else result
result = np.dot(result, np.array([[1], [1]]))
return result[0][0]
代码中增加
数组保存中间的计算状态,其中
表示
的值。该形式的矩阵运算时间复杂度为
,空间复杂度为
。
当步长调整为 1~
阶时,问走到
阶一共多少种走法
def climbingStairs(n, m):
if n == 0:
return 1
stepSize, result = n if m >= n else m, 0
for i in range(1, stepSize + 1):
result += climbingStairs4(n - i, m)
return result
递归关系式由
更新为
,增加步长
与
的大小关系判断。
def climbingStairs(n, m):
if n <= 1 or m == 1:
return 1
stepSize = n if m >= n else m
arr = [0] * stepSize
arr[0], arr[1], index, tmp = 1, 1, 2, 1
while index <= n:
if index > stepSize:
tmp, arr[index % stepSize] = arr[index % stepSize], arr[(index - 1) % stepSize] * 2 - tmp
else:
arr[index % stepSize] = arr[(index - 1) % stepSize] * 2
index += 1
return arr[(index - 1) % stepSize]
时间复杂度与步长为 1 ~ 2 时相同,为
。因为需要申请空间存储中间状态数据,所以空间复杂度为
,其中
表示最大步长。