整理动态规划笔记(c 和 cpp 版)

爬楼梯

一:动态规划

优化之后的程序

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 次方的形式,那么可以用快速幂来加速计算

  • 如果一个递归式形如f(n) = \sum_{i=1}^{m}{a_if(n-i)},即齐次线性递推式,我们就可以把数列的递推关系转化为矩阵的递推关系,即构造出一个矩阵的 n 次方乘以一个列向量得到一个列向量,这个列向量中包含我们要求的 f(n) .一般情况下,形如f(n) = \sum_{i-1}^{m}{a_if(n-i)} 可以构造出这样的m x m 的矩阵:

 

 

f(x) = (2x-6)c+f(x-1)+f(x-2)+f(x-3)

  • 那么遇到非齐次线性递推我们是不是就束手无策了呢?其实未必。有些时候我们可以把非齐次线性递推转化为齐次线性递推,比如这样一个递推:
 

我们可以做这样的变换:

 

令xg(x) = f(x)+xc,那么我们又得到了齐次线性递推:

 

于是就可以使用矩阵快速幂求解了。当然并不是所有非齐次线性都可以化成齐次线性,我们还是要具体问题具体分析。

以下是矩阵快速幂的代码实现

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)是齐次线性递推,根据递推方程f(n) = f(n-1)+f(n-2),我们可以写出这样的特征方程:

 

求得x_1 = \frac{1+\sqrt5}{2}x_2 = \frac{1-\sqrt5}{2}设通解为f(n)=c_1x_1^n+c_2x_2^n,代入初始条件f(1)=1f(2)=1,得c_1=\frac{1}{\sqrt5}c_2=\frac{-1}{\sqrt5},我们得到了这个递推数列的通项公式:

 

接着我们就可以通过这个公式直接求第 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),存在很多冗余计算

  • 一般情况下,我们使用「记忆化搜索」或者「迭代」的方法

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值