爬楼梯
一:动态规划
优化之后的程序
int climbStairs(int n) {
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
二:矩阵快速幂(参考力扣)
以上的方法适用于 n 比较小的情况,在 n 变大之后,O(n) 的时间复杂度会让这个算法看起来有些捉襟见肘。我们可以用「矩阵快速幂」的方法来优化这个过程
得
令
因此我们只要能快速计算矩阵 M的 n 次幂,就可以得到 f(n) 的值。如果直接求取 ,时间复杂度是 O(n) 的,我们可以定义矩阵乘法,然后用快速幂算法来加速这里
如何想到使用矩阵快速幂?
-
如果一个问题可与转化为求解一个矩阵的 n 次方的形式,那么可以用快速幂来加速计算
-
如果一个递归式形如,即齐次线性递推式,我们就可以把数列的递推关系转化为矩阵的递推关系,即构造出一个矩阵的 n 次方乘以一个列向量得到一个列向量,这个列向量中包含我们要求的 f(n) .一般情况下,形如 可以构造出这样的m x m 的矩阵:
- 那么遇到非齐次线性递推我们是不是就束手无策了呢?其实未必。有些时候我们可以把非齐次线性递推转化为齐次线性递推,比如这样一个递推:
我们可以做这样的变换:
令x,那么我们又得到了齐次线性递推:
于是就可以使用矩阵快速幂求解了。当然并不是所有非齐次线性都可以化成齐次线性,我们还是要具体问题具体分析。
以下是矩阵快速幂的代码实现
struct Matrix {
long long mat[2][2];
};
struct Matrix multiply(struct Matrix a, struct Matrix b) { // 矩阵 a*b
struct Matrix c;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c.mat[i][j] = a.mat[i][0] * b.mat[0][j] + a.mat[i][1] * b.mat[1][j];
}
}
return c;
}
struct Matrix matrixPow(struct Matrix a, int n) { // a 是 f(1) f(2)
struct Matrix ret; // M
ret.mat[0][0] = ret.mat[1][1] = 1;
ret.mat[0][1] = ret.mat[1][0] = 0;
while (n > 0) {
if ((n & 1) == 1) {
ret = multiply(ret, a); // ret = ret*a
}
n >>= 1; // -->n/2
a = multiply(a, a);
}
return ret;
}
int climbStairs(int n) {
struct Matrix ret;
ret.mat[1][1] = 0;
ret.mat[0][0] = ret.mat[0][1] = ret.mat[1][0] = 1;
struct Matrix res = matrixPow(ret, n); // ret 的 n 次方
return res.mat[0][0];
}
三:通项公式
之前的方法我们已经讨论了f(n)是齐次线性递推,根据递推方程,我们可以写出这样的特征方程:
求得,设通解为,代入初始条件,,得,,我们得到了这个递推数列的通项公式:
接着我们就可以通过这个公式直接求第 n 项了
int climbStairs(int n) {
double sqrt5 = sqrt(5);
double fibn = pow((1 + sqrt5) / 2, n + 1) - pow((1 - sqrt5) / 2, n + 1);
return (int) round(fibn / sqrt5);
}
总结:
-
n比较小的时候,我们直接使用过递归法求解, 不做任何记忆化操作,时间复杂度是O(2^n),存在很多冗余计算
-
一般情况下,我们使用「记忆化搜索」或者「迭代」的方法